diff options
Diffstat (limited to 'zellij-utils/src/kdl/kdl_layout_parser.rs')
-rw-r--r-- | zellij-utils/src/kdl/kdl_layout_parser.rs | 577 |
1 files changed, 480 insertions, 97 deletions
diff --git a/zellij-utils/src/kdl/kdl_layout_parser.rs b/zellij-utils/src/kdl/kdl_layout_parser.rs index fad5fafed..7125d1fe8 100644 --- a/zellij-utils/src/kdl/kdl_layout_parser.rs +++ b/zellij-utils/src/kdl/kdl_layout_parser.rs @@ -2,14 +2,15 @@ use crate::input::{ command::RunCommand, config::ConfigError, layout::{ - FloatingPanesLayout, Layout, PaneLayout, PercentOrFixed, Run, RunPlugin, RunPluginLocation, - SplitDirection, SplitSize, + FloatingPaneLayout, Layout, LayoutConstraint, PercentOrFixed, Run, RunPlugin, + RunPluginLocation, SplitDirection, SplitSize, SwapFloatingLayout, SwapTiledLayout, + TiledPaneLayout, }, }; use kdl::*; -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeMap, HashMap, HashSet}; use std::str::FromStr; use crate::{ @@ -28,17 +29,17 @@ use url::Url; #[derive(Debug, Clone, PartialEq, Eq)] pub enum PaneOrFloatingPane { - Pane(PaneLayout), - FloatingPane(FloatingPanesLayout), - Either(PaneLayout), + Pane(TiledPaneLayout), + FloatingPane(FloatingPaneLayout), + Either(TiledPaneLayout), } pub struct KdlLayoutParser<'a> { global_cwd: Option<PathBuf>, raw_layout: &'a str, - tab_templates: HashMap<String, (PaneLayout, Vec<FloatingPanesLayout>, KdlNode)>, + tab_templates: HashMap<String, (TiledPaneLayout, Vec<FloatingPaneLayout>, KdlNode)>, pane_templates: HashMap<String, (PaneOrFloatingPane, KdlNode)>, - default_tab_template: Option<(PaneLayout, Vec<FloatingPanesLayout>, KdlNode)>, + default_tab_template: Option<(TiledPaneLayout, Vec<FloatingPaneLayout>, KdlNode)>, } impl<'a> KdlLayoutParser<'a> { @@ -71,6 +72,8 @@ impl<'a> KdlLayoutParser<'a> { || word == "size" || word == "cwd" || word == "split_direction" + || word == "swap_tiled_layout" + || word == "swap_floating_layout" } fn is_a_valid_pane_property(&self, property_name: &str) -> bool { property_name == "borderless" @@ -110,6 +113,9 @@ impl<'a> KdlLayoutParser<'a> { || property_name == "split_direction" || property_name == "cwd" || property_name == "floating_panes" + || property_name == "children" + || property_name == "max_panes" + || property_name == "min_panes" } fn assert_legal_node_name(&self, name: &str, kdl_node: &KdlNode) -> Result<(), ConfigError> { if name.contains(char::is_whitespace) { @@ -402,7 +408,7 @@ impl<'a> KdlLayoutParser<'a> { } Ok(run) } - fn parse_pane_node(&self, kdl_node: &KdlNode) -> Result<PaneLayout, ConfigError> { + fn parse_pane_node(&self, kdl_node: &KdlNode) -> Result<TiledPaneLayout, ConfigError> { self.assert_valid_pane_properties(kdl_node)?; 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"); @@ -411,12 +417,13 @@ impl<'a> KdlLayoutParser<'a> { let split_size = self.parse_split_size(kdl_node)?; let run = self.parse_command_plugin_or_edit_block(kdl_node)?; let children_split_direction = self.parse_split_direction(kdl_node)?; - let (external_children_index, children) = match kdl_children_nodes!(kdl_node) { - Some(children) => self.parse_child_pane_nodes_for_pane(&children)?, - None => (None, vec![]), - }; + let (external_children_index, children_are_stacked, children) = + match kdl_children_nodes!(kdl_node) { + Some(children) => self.parse_child_pane_nodes_for_pane(&children)?, + None => (None, false, vec![]), + }; self.assert_no_mixed_children_and_properties(kdl_node)?; - Ok(PaneLayout { + Ok(TiledPaneLayout { borderless: borderless.unwrap_or_default(), focus, name, @@ -425,13 +432,14 @@ impl<'a> KdlLayoutParser<'a> { children_split_direction, external_children_index, children, + children_are_stacked, ..Default::default() }) } fn parse_floating_pane_node( &self, kdl_node: &KdlNode, - ) -> Result<FloatingPanesLayout, ConfigError> { + ) -> Result<FloatingPaneLayout, ConfigError> { self.assert_valid_floating_pane_properties(kdl_node)?; let height = self.parse_percent_or_fixed(kdl_node, "height", false)?; let width = self.parse_percent_or_fixed(kdl_node, "width", false)?; @@ -442,7 +450,7 @@ impl<'a> KdlLayoutParser<'a> { let name = kdl_get_string_property_or_child_value_with_error!(kdl_node, "name") .map(|name| name.to_string()); self.assert_no_mixed_children_and_properties(kdl_node)?; - Ok(FloatingPanesLayout { + Ok(FloatingPaneLayout { name, height, width, @@ -456,19 +464,21 @@ impl<'a> KdlLayoutParser<'a> { fn insert_children_to_pane_template( &self, kdl_node: &KdlNode, - pane_template: &mut PaneLayout, + pane_template: &mut TiledPaneLayout, 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![]), - }; + let (external_children_index, children_are_stacked, pane_parts) = + match kdl_children_nodes!(kdl_node) { + Some(children) => self.parse_child_pane_nodes_for_pane(&children)?, + None => (None, false, vec![]), + }; if pane_parts.len() > 0 { - let child_panes_layout = PaneLayout { + let child_panes_layout = TiledPaneLayout { children_split_direction, children: pane_parts, external_children_index, + children_are_stacked, ..Default::default() }; self.assert_one_children_block(&pane_template, pane_template_kdl_node)?; @@ -480,12 +490,47 @@ impl<'a> KdlLayoutParser<'a> { } Ok(()) } + fn populate_external_children_index( + &self, + kdl_node: &KdlNode, + ) -> Result<Option<(usize, bool)>, ConfigError> { + // Option<(external_children_index, is_stacked)> + if let Some(pane_child_nodes) = kdl_children_nodes!(kdl_node) { + for (i, child) in pane_child_nodes.iter().enumerate() { + if kdl_name!(child) == "children" { + let stacked = + kdl_get_bool_property_or_child_value_with_error!(kdl_node, "stacked") + .unwrap_or(false); + if let Some(grand_children) = kdl_children_nodes!(child) { + let grand_children: Vec<&str> = grand_children + .iter() + .map(|g| kdl_name!(g)) + .filter(|g| g != &"stacked") + .collect(); + if !grand_children.is_empty() { + return Err(ConfigError::new_layout_kdl_error( + format!( + "Invalid `children` properties: {}", + grand_children.join(", ") + ), + child.span().offset(), + child.span().len(), + )); + } + } + return Ok(Some((i, stacked))); + } + } + } + return Ok(None); + } fn parse_pane_node_with_template( &self, kdl_node: &KdlNode, pane_template: PaneOrFloatingPane, + should_mark_external_children_index: bool, pane_template_kdl_node: &KdlNode, - ) -> Result<PaneLayout, ConfigError> { + ) -> Result<TiledPaneLayout, ConfigError> { match pane_template { PaneOrFloatingPane::Pane(mut pane_template) | PaneOrFloatingPane::Either(mut pane_template) => { @@ -501,6 +546,15 @@ impl<'a> KdlLayoutParser<'a> { kdl_get_bool_property_or_child_value_with_error!(kdl_node, "start_suspended"); let split_size = self.parse_split_size(kdl_node)?; let run = self.parse_command_plugin_or_edit_block_for_template(kdl_node)?; + + // TODO: change should_insert_children to should_keep_pane_external_children_index + // or smth + let external_children_index_and_is_stacked = if should_mark_external_children_index + { + self.populate_external_children_index(kdl_node)? + } else { + None + }; self.assert_no_bare_attributes_in_pane_node_with_template( &run, &pane_template.run, @@ -537,9 +591,13 @@ impl<'a> KdlLayoutParser<'a> { if let Some(index_of_children) = pane_template.external_children_index { pane_template .children - .insert(index_of_children, PaneLayout::default()); + .insert(index_of_children, TiledPaneLayout::default()); } - pane_template.external_children_index = None; + pane_template.external_children_index = + external_children_index_and_is_stacked.map(|(index, _is_stacked)| index); + pane_template.children_are_stacked = external_children_index_and_is_stacked + .map(|(_index, is_stacked)| is_stacked) + .unwrap_or(false); Ok(pane_template) }, PaneOrFloatingPane::FloatingPane(_) => { @@ -561,7 +619,7 @@ impl<'a> KdlLayoutParser<'a> { kdl_node: &KdlNode, pane_template: PaneOrFloatingPane, pane_template_kdl_node: &KdlNode, - ) -> Result<FloatingPanesLayout, ConfigError> { + ) -> Result<FloatingPaneLayout, ConfigError> { match pane_template { PaneOrFloatingPane::Pane(_) => { let pane_template_name = kdl_get_string_property_or_child_value_with_error!( @@ -611,7 +669,7 @@ impl<'a> KdlLayoutParser<'a> { let width = self.parse_percent_or_fixed(kdl_node, "width", false)?; let x = self.parse_percent_or_fixed(kdl_node, "x", true)?; let y = self.parse_percent_or_fixed(kdl_node, "y", true)?; - // let mut floating_pane = FloatingPanesLayout::from(&pane_template); + // let mut floating_pane = FloatingPaneLayout::from(&pane_template); if let Some(height) = height { pane_template.height = Some(height); } @@ -662,7 +720,7 @@ impl<'a> KdlLayoutParser<'a> { let width = self.parse_percent_or_fixed(kdl_node, "width", false)?; let x = self.parse_percent_or_fixed(kdl_node, "x", true)?; let y = self.parse_percent_or_fixed(kdl_node, "y", true)?; - let mut floating_pane = FloatingPanesLayout::from(&pane_template); + let mut floating_pane = FloatingPaneLayout::from(&pane_template); if let Some(height) = height { floating_pane.height = Some(height); } @@ -812,7 +870,7 @@ impl<'a> KdlLayoutParser<'a> { self.pane_templates.insert( template_name, ( - PaneOrFloatingPane::Either(PaneLayout { + PaneOrFloatingPane::Either(TiledPaneLayout { focus, run, ..Default::default() @@ -830,7 +888,7 @@ impl<'a> KdlLayoutParser<'a> { self.pane_templates.insert( template_name, ( - PaneOrFloatingPane::FloatingPane(FloatingPanesLayout { + PaneOrFloatingPane::FloatingPane(FloatingPaneLayout { focus, run, height, @@ -849,15 +907,16 @@ impl<'a> KdlLayoutParser<'a> { kdl_get_bool_property_or_child_value_with_error!(kdl_node, "borderless"); let split_size = self.parse_split_size(kdl_node)?; 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![]), - }; + let (external_children_index, children_are_stacked, pane_parts) = + match kdl_children_nodes!(kdl_node) { + Some(children) => self.parse_child_pane_nodes_for_pane(&children)?, + None => (None, false, vec![]), + }; self.assert_no_mixed_children_and_properties(kdl_node)?; self.pane_templates.insert( template_name, ( - PaneOrFloatingPane::Pane(PaneLayout { + PaneOrFloatingPane::Pane(TiledPaneLayout { borderless: borderless.unwrap_or_default(), focus, split_size, @@ -865,6 +924,7 @@ impl<'a> KdlLayoutParser<'a> { children_split_direction, external_children_index, children: pane_parts, + children_are_stacked, ..Default::default() }), kdl_node.clone(), @@ -877,8 +937,16 @@ impl<'a> KdlLayoutParser<'a> { fn parse_tab_node( &mut self, kdl_node: &KdlNode, - ) -> Result<(bool, Option<String>, PaneLayout, Vec<FloatingPanesLayout>), ConfigError> { - // (is_focused, Option<tab_name>, PaneLayout, Vec<FloatingPanesLayout>) + ) -> Result< + ( + bool, + Option<String>, + TiledPaneLayout, + Vec<FloatingPaneLayout>, + ), + ConfigError, + > { + // (is_focused, Option<tab_name>, PaneLayout, Vec<FloatingPaneLayout>) self.assert_valid_tab_properties(kdl_node)?; let tab_name = kdl_get_string_property_or_child_value!(kdl_node, "name").map(|s| s.to_string()); @@ -889,11 +957,16 @@ impl<'a> KdlLayoutParser<'a> { let mut child_floating_panes = vec![]; let children = match kdl_children_nodes!(kdl_node) { Some(children) => { - self.parse_child_pane_nodes_for_tab(children, &mut child_floating_panes)? + let should_mark_external_children_index = false; + self.parse_child_pane_nodes_for_tab( + children, + should_mark_external_children_index, + &mut child_floating_panes, + )? }, None => vec![], }; - let mut pane_layout = PaneLayout { + let mut pane_layout = TiledPaneLayout { children_split_direction, children, ..Default::default() @@ -906,8 +979,9 @@ impl<'a> KdlLayoutParser<'a> { fn parse_child_pane_nodes_for_tab( &self, children: &[KdlNode], - child_floating_panes: &mut Vec<FloatingPanesLayout>, - ) -> Result<Vec<PaneLayout>, ConfigError> { + should_mark_external_children_index: bool, + child_floating_panes: &mut Vec<FloatingPaneLayout>, + ) -> Result<Vec<TiledPaneLayout>, ConfigError> { let mut nodes = vec![]; for child in children { if kdl_name!(child) == "pane" { @@ -918,6 +992,7 @@ impl<'a> KdlLayoutParser<'a> { nodes.push(self.parse_pane_node_with_template( child, pane_template, + should_mark_external_children_index, &pane_template_kdl_node, )?); } else if kdl_name!(child) == "floating_panes" { @@ -937,37 +1012,51 @@ impl<'a> KdlLayoutParser<'a> { } } if nodes.is_empty() { - nodes.push(PaneLayout::default()); + nodes.push(TiledPaneLayout::default()); } Ok(nodes) } fn parse_child_pane_nodes_for_pane( &self, children: &[KdlNode], - ) -> Result<(Option<usize>, Vec<PaneLayout>), ConfigError> { - // usize is external_children_index + ) -> Result<(Option<usize>, bool, Vec<TiledPaneLayout>), ConfigError> { + // usize is external_children_index, bool is "children_are_stacked" let mut external_children_index = None; + let mut children_are_stacked = false; let mut nodes = vec![]; for (i, child) in children.iter().enumerate() { 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_layout_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(), - )); + let stacked = kdl_get_bool_property_or_child_value_with_error!(child, "stacked") + .unwrap_or(false); + if let Some(grand_children) = kdl_children_nodes!(child) { + let grand_children: Vec<&str> = grand_children + .iter() + .map(|g| kdl_name!(g)) + .filter(|g| g != &"stacked") + .collect(); + if !grand_children.is_empty() { + return Err(ConfigError::new_layout_kdl_error( + format!( + "Invalid `children` properties: {}", + grand_children.join(", ") + ), + child.span().offset(), + child.span().len(), + )); + } } external_children_index = Some(i); + children_are_stacked = stacked; } else if let Some((pane_template, pane_template_kdl_node)) = self.pane_templates.get(kdl_name!(child)).cloned() { + let should_mark_external_children_index = false; nodes.push(self.parse_pane_node_with_template( child, pane_template, + should_mark_external_children_index, &pane_template_kdl_node, )?); } else if !self.is_a_valid_pane_property(kdl_name!(child)) { @@ -978,7 +1067,7 @@ impl<'a> KdlLayoutParser<'a> { )); } } - Ok((external_children_index, nodes)) + Ok((external_children_index, children_are_stacked, nodes)) } fn has_child_nodes(&self, kdl_node: &KdlNode) -> bool { if let Some(children) = kdl_children_nodes!(kdl_node) { @@ -1076,7 +1165,7 @@ impl<'a> KdlLayoutParser<'a> { } fn assert_one_children_block( &self, - layout: &PaneLayout, + layout: &TiledPaneLayout, kdl_node: &KdlNode, ) -> Result<(), ConfigError> { let children_block_count = layout.children_block_count(); @@ -1193,8 +1282,6 @@ impl<'a> KdlLayoutParser<'a> { ) -> Result<(), ConfigError> { let has_borderless_prop = 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_cwd_prop = kdl_get_string_property_or_child_value_with_error!(kdl_node, "cwd").is_some(); let has_non_cwd_run_prop = self @@ -1206,15 +1293,12 @@ impl<'a> KdlLayoutParser<'a> { .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_non_cwd_run_prop || has_cwd_prop) + && (has_borderless_prop || has_non_cwd_run_prop || has_cwd_prop) { let mut offending_nodes = vec![]; if has_borderless_prop { offending_nodes.push("borderless"); } - if has_focus_prop { - offending_nodes.push("focus"); - } if has_non_cwd_run_prop { offending_nodes.push("command/edit/plugin"); } @@ -1235,8 +1319,8 @@ impl<'a> KdlLayoutParser<'a> { } fn insert_layout_children_or_error( &self, - layout: &mut PaneLayout, - mut child_panes_layout: PaneLayout, + layout: &mut TiledPaneLayout, + mut child_panes_layout: TiledPaneLayout, kdl_node: &KdlNode, ) -> Result<(), ConfigError> { let successfully_inserted = layout.insert_children_layout(&mut child_panes_layout)?; @@ -1253,11 +1337,20 @@ impl<'a> KdlLayoutParser<'a> { fn parse_tab_node_with_template( &self, kdl_node: &KdlNode, - mut tab_layout: PaneLayout, - mut tab_template_floating_panes: Vec<FloatingPanesLayout>, + mut tab_layout: TiledPaneLayout, + mut tab_template_floating_panes: Vec<FloatingPaneLayout>, + should_mark_external_children_index: bool, tab_layout_kdl_node: &KdlNode, - ) -> Result<(bool, Option<String>, PaneLayout, Vec<FloatingPanesLayout>), ConfigError> { - // (is_focused, Option<tab_name>, PaneLayout, Vec<FloatingPanesLayout>) + ) -> Result< + ( + bool, + Option<String>, + TiledPaneLayout, + Vec<FloatingPaneLayout>, + ), + ConfigError, + > { + // (is_focused, Option<tab_name>, PaneLayout, Vec<FloatingPaneLayout>) let tab_name = kdl_get_string_property_or_child_value!(kdl_node, "name").map(|s| s.to_string()); let tab_cwd = @@ -1266,9 +1359,12 @@ impl<'a> KdlLayoutParser<'a> { 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, &mut tab_template_floating_panes)?; - let child_panes_layout = PaneLayout { + let child_panes = self.parse_child_pane_nodes_for_tab( + children, + should_mark_external_children_index, + &mut tab_template_floating_panes, + )?; + let child_panes_layout = TiledPaneLayout { children_split_direction, children: child_panes, ..Default::default() @@ -1284,7 +1380,7 @@ impl<'a> KdlLayoutParser<'a> { if let Some(index_of_children) = tab_layout.external_children_index { tab_layout .children - .insert(index_of_children, PaneLayout::default()); + .insert(index_of_children, TiledPaneLayout::default()); } }, } @@ -1342,7 +1438,7 @@ impl<'a> KdlLayoutParser<'a> { fn parse_tab_template_node( &self, kdl_node: &KdlNode, - ) -> Result<(PaneLayout, Vec<FloatingPanesLayout>), ConfigError> { + ) -> Result<(TiledPaneLayout, Vec<FloatingPaneLayout>), ConfigError> { self.assert_valid_tab_properties(kdl_node)?; let children_split_direction = self.parse_split_direction(kdl_node)?; let mut tab_children = vec![]; @@ -1368,9 +1464,11 @@ impl<'a> KdlLayoutParser<'a> { } else if let Some((pane_template, pane_template_kdl_node)) = self.pane_templates.get(kdl_name!(child)).cloned() { + let should_mark_external_children_index = false; tab_children.push(self.parse_pane_node_with_template( child, pane_template, + should_mark_external_children_index, &pane_template_kdl_node, )?); } else if kdl_name!(child) == "floating_panes" { @@ -1392,7 +1490,7 @@ impl<'a> KdlLayoutParser<'a> { } } Ok(( - PaneLayout { + TiledPaneLayout { children_split_direction, children: tab_children, external_children_index, @@ -1401,14 +1499,14 @@ impl<'a> KdlLayoutParser<'a> { tab_floating_children, )) } - fn default_template(&self) -> Result<Option<PaneLayout>, ConfigError> { + fn default_template(&self) -> Result<Option<TiledPaneLayout>, ConfigError> { match &self.default_tab_template { Some((template, _template_floating_panes, _kdl_node)) => { let mut template = template.clone(); if let Some(children_index) = template.external_children_index { template .children - .insert(children_index, PaneLayout::default()) + .insert(children_index, TiledPaneLayout::default()) } template.external_children_index = None; Ok(Some(template)) @@ -1545,28 +1643,230 @@ impl<'a> KdlLayoutParser<'a> { } Ok(()) } + fn populate_swap_tiled_layouts( + &mut self, + layout_children: &[KdlNode], + swap_tiled_layouts: &mut Vec<SwapTiledLayout>, + ) -> Result<(), ConfigError> { + for child in layout_children.iter() { + let child_name = kdl_name!(child); + if child_name == "swap_tiled_layout" { + let swap_layout_name = + kdl_get_string_property_or_child_value!(child, "name").map(|n| String::from(n)); + if let Some(swap_tiled_layout_group) = kdl_children_nodes!(child) { + let mut swap_tiled_layout = BTreeMap::new(); + for layout in swap_tiled_layout_group { + let layout_node_name = kdl_name!(layout); + if layout_node_name == "tab" { + let layout_constraint = self.parse_constraint(layout)?; + + match &self.default_tab_template { + Some(( + default_tab_template, + _default_tab_template_floating_panes, + default_tab_template_kdl_node, + )) => { + let default_tab_template = default_tab_template.clone(); + let layout = self + .populate_one_swap_tiled_layout_with_template( + layout, + default_tab_template, + default_tab_template_kdl_node.clone(), + )?; + swap_tiled_layout.insert(layout_constraint, layout); + }, + None => { + let layout = self.populate_one_swap_tiled_layout(layout)?; + swap_tiled_layout.insert(layout_constraint, layout); + }, + } + } else if let Some(( + tab_template, + _tab_template_floating_panes, + tab_template_kdl_node, + )) = self.tab_templates.get(layout_node_name).cloned() + { + let layout_constraint = self.parse_constraint(layout)?; |