use crate::input::{ command::RunCommand, config::ConfigError, layout::{ FloatingPanesLayout, Layout, PaneLayout, PercentOrFixed, Run, RunPlugin, RunPluginLocation, SplitDirection, SplitSize, }, }; use kdl::*; use std::collections::{HashMap, HashSet}; 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_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, }; use std::convert::TryFrom; use std::path::PathBuf; use std::vec::Vec; use url::Url; #[derive(Debug, Clone, PartialEq, Eq)] pub enum PaneOrFloatingPane { Pane(PaneLayout), FloatingPane(FloatingPanesLayout), Either(PaneLayout), } pub struct KdlLayoutParser<'a> { global_cwd: Option, raw_layout: &'a str, tab_templates: HashMap, KdlNode)>, pane_templates: HashMap, default_tab_template: Option<(PaneLayout, Vec, KdlNode)>, } impl<'a> KdlLayoutParser<'a> { pub fn new(raw_layout: &'a str, global_cwd: Option) -> 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 { word == "pane" || word == "layout" || word == "pane_template" || word == "tab_template" || word == "default_tab_template" || word == "command" || word == "edit" || word == "plugin" || word == "children" || word == "tab" || word == "args" || word == "close_on_exit" || word == "start_suspended" || word == "borderless" || word == "focus" || word == "name" || word == "size" || word == "cwd" || word == "split_direction" } fn is_a_valid_pane_property(&self, property_name: &str) -> bool { property_name == "borderless" || property_name == "focus" || property_name == "name" || property_name == "size" || property_name == "plugin" || property_name == "command" || property_name == "edit" || property_name == "cwd" || property_name == "args" || property_name == "close_on_exit" || property_name == "start_suspended" || property_name == "split_direction" || property_name == "pane" || property_name == "children" } fn is_a_valid_floating_pane_property(&self, property_name: &str) -> bool { property_name == "borderless" || property_name == "focus" || property_name == "name" || property_name == "plugin" || property_name == "command" || property_name == "edit" || property_name == "cwd" || property_name == "args" || property_name == "close_on_exit" || property_name == "start_suspended" || property_name == "x" || property_name == "y" || property_name == "width" || property_name == "height" } fn is_a_valid_tab_property(&self, property_name: &str) -> bool { property_name == "focus" || property_name == "name" || property_name == "split_direction" || property_name == "cwd" || property_name == "floating_panes" } fn assert_legal_node_name(&self, name: &str, kdl_node: &KdlNode) -> Result<(), ConfigError> { if name.contains(char::is_whitespace) { Err(ConfigError::new_layout_kdl_error( format!("Node names ({}) cannot contain whitespace.", name), kdl_node.span().offset(), kdl_node.span().len(), )) } else if self.is_a_reserved_word(&name) { Err(ConfigError::new_layout_kdl_error( format!("Node name '{}' is a reserved word.", name), kdl_node.span().offset(), kdl_node.span().len(), )) } else { Ok(()) } } fn assert_legal_template_name( &self, name: &str, kdl_node: &KdlNode, ) -> Result<(), ConfigError> { if name.is_empty() { Err(ConfigError::new_layout_kdl_error( format!("Template names cannot be empty"), kdl_node.span().offset(), kdl_node.span().len(), )) } else if name.contains(')') || name.contains('(') { Err(ConfigError::new_layout_kdl_error( format!("Template names cannot contain parantheses"), kdl_node.span().offset(), kdl_node.span().len(), )) } else if name .chars() .next() .map(|first_char| first_char.is_numeric()) .unwrap_or(false) { Err(ConfigError::new_layout_kdl_error( format!("Template names cannot start with numbers"), kdl_node.span().offset(), kdl_node.span().len(), )) } else { Ok(()) } } fn parse_split_size(&self, kdl_node: &KdlNode) -> Result, ConfigError> { if let Some(size) = kdl_get_string_property_or_child_value!(kdl_node, "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!( format!("size should be a fixed number (eg. 1) or a quoted percent (eg. \"50%\")"), node )) } else if let Some(node) = kdl_child_with_name!(kdl_node, "size") { Err(kdl_parsing_error!( format!( "size cannot be bare, it should have a value (eg. 'size 1', or 'size \"50%\"')" ), node )) } else { Ok(None) } } fn parse_percent_or_fixed( &self, kdl_node: &KdlNode, value_name: &str, can_be_zero: bool, ) -> Result, ConfigError> { if let Some(size) = kdl_get_string_property_or_child_value!(kdl_node, value_name) { match PercentOrFixed::from_str(size) { Ok(size) => { if !can_be_zero && size.is_zero() { Err(kdl_parsing_error!( format!("{} should be greater than 0", value_name), kdl_node )) } else { Ok(Some(size)) } }, Err(_e) => Err(kdl_parsing_error!( format!( "{} should be a fixed number (eg. 1) or a quoted percent (eg. \"50%\")", value_name ), kdl_node )), } } else if let Some(size) = kdl_get_int_property_or_child_value!(kdl_node, value_name) { if size == 0 && !can_be_zero { return Err(kdl_parsing_error!( format!("{} should be greater than 0", value_name), kdl_node )); } Ok(Some(PercentOrFixed::Fixed(size as usize))) } else if let Some(node) = kdl_property_or_child_value_node!(kdl_node, "size") { Err(kdl_parsing_error!( format!( "{} should be a fixed number (eg. 1) or a quoted percent (eg. \"50%\")", value_name ), node )) } else if let Some(node) = kdl_child_with_name!(kdl_node, "size") { Err(kdl_parsing_error!( format!( "{} cannot be bare, it should have a value (eg. 'size 1', or 'size \"50%\"')", value_name ), node )) } else { Ok(None) } } fn parse_plugin_block(&self, plugin_block: &KdlNode) -> Result, ConfigError> { let _allow_exec_host_cmd = kdl_get_bool_property_or_child_value_with_error!(plugin_block, "_allow_exec_host_cmd") .unwrap_or(false); let string_url = kdl_get_string_property_or_child_value_with_error!(plugin_block, "location").ok_or( ConfigError::new_layout_kdl_error( "Plugins must have a location".into(), plugin_block.span().offset(), plugin_block.span().len(), ), )?; let url_node = kdl_get_property_or_child!(plugin_block, "location").ok_or( ConfigError::new_layout_kdl_error( "Plugins must have a location".into(), plugin_block.span().offset(), plugin_block.span().len(), ), )?; let url = Url::parse(string_url).map_err(|e| { ConfigError::new_layout_kdl_error( format!("Failed to parse url: {:?}", e), url_node.span().offset(), url_node.span().len(), ) })?; let location = RunPluginLocation::try_from(url)?; Ok(Some(Run::Plugin(RunPlugin { _allow_exec_host_cmd, location, }))) } fn parse_args(&self, pane_node: &KdlNode) -> Result>, 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)); } Ok(Some( kdl_string_arguments!(kdl_args) .iter() .map(|s| String::from(*s)) .collect(), )) }, None => Ok(None), } } fn cwd_prefix(&self, tab_cwd: Option<&PathBuf>) -> Result, ConfigError> { Ok(match (&self.global_cwd, tab_cwd) { (Some(global_cwd), Some(tab_cwd)) => Some(global_cwd.join(tab_cwd)), (None, Some(tab_cwd)) => Some(tab_cwd.clone()), (Some(global_cwd), None) => Some(global_cwd.clone()), (None, None) => None, }) } fn parse_cwd(&self, kdl_node: &KdlNode) -> Result, ConfigError> { Ok( kdl_get_string_property_or_child_value_with_error!(kdl_node, "cwd") .map(|cwd| PathBuf::from(cwd)), ) } fn parse_pane_command( &self, pane_node: &KdlNode, is_template: bool, ) -> Result, ConfigError> { let command = kdl_get_string_property_or_child_value_with_error!(pane_node, "command") .map(|c| PathBuf::from(c)); let edit = kdl_get_string_property_or_child_value_with_error!(pane_node, "edit") .map(|c| PathBuf::from(c)); let cwd = self.parse_cwd(pane_node)?; let args = self.parse_args(pane_node)?; let close_on_exit = kdl_get_bool_property_or_child_value_with_error!(pane_node, "close_on_exit"); let start_suspended = kdl_get_bool_property_or_child_value_with_error!(pane_node, "start_suspended"); if !is_template { self.assert_no_bare_attributes_in_pane_node( &command, &args, &close_on_exit, &start_suspended, pane_node, )?; } let hold_on_close = close_on_exit.map(|c| !c).unwrap_or(true); let hold_on_start = start_suspended.map(|c| c).unwrap_or(false); match (command, edit, cwd) { (None, None, Some(cwd)) => Ok(Some(Run::Cwd(cwd))), (Some(command), None, cwd) => Ok(Some(Run::Command(RunCommand { command, args: args.unwrap_or_else(|| vec![]), cwd, hold_on_close, hold_on_start, }))), (None, Some(edit), Some(cwd)) => Ok(Some(Run::EditFile(cwd.join(edit), None))), (None, Some(edit), None) => Ok(Some(Run::EditFile(edit, None))), (Some(_command), Some(_edit), _) => Err(ConfigError::new_layout_kdl_error( "cannot have both a command and an edit instruction for the same pane".into(), pane_node.span().offset(), pane_node.span().len(), )), _ => Ok(None), } } fn parse_command_plugin_or_edit_block( &self, kdl_node: &KdlNode, ) -> Result, ConfigError> { let mut run = self.parse_pane_command(kdl_node, false)?; if let Some(plugin_block) = kdl_get_child!(kdl_node, "plugin") { let has_non_cwd_run_prop = run .map(|r| match r { Run::Cwd(_) => false, _ => true, }) .unwrap_or(false); if has_non_cwd_run_prop { return Err(ConfigError::new_layout_kdl_error( "Cannot have both a command/edit 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_plugin_or_edit_block_for_template( &self, kdl_node: &KdlNode, ) -> Result, ConfigError> { let mut run = self.parse_pane_command(kdl_node, true)?; if let Some(plugin_block) = kdl_get_child!(kdl_node, "plugin") { let has_non_cwd_run_prop = run .map(|r| match r { Run::Cwd(_) => false, _ => true, }) .unwrap_or(false); if has_non_cwd_run_prop { return Err(ConfigError::new_layout_kdl_error( "Cannot have both a command/edit 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_pane_node(&self, kdl_node: &KdlNode) -> Result { 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"); let name = kdl_get_string_property_or_child_value_with_error!(kdl_node, "name") .map(|name| name.to_string()); 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![]), }; self.assert_no_mixed_children_and_properties(kdl_node)?; Ok(PaneLayout { borderless: borderless.unwrap_or_default(), focus, name, split_size, run, children_split_direction, external_children_index, children, ..Default::default() }) } fn parse_floating_pane_node( &self, kdl_node: &KdlNode, ) -> Result { 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)?; let x = self.parse_percent_or_fixed(kdl_node, "x", true)?; let y = self.parse_percent_or_fixed(kdl_node, "y", true)?; let run = self.parse_command_plugin_or_edit_block(kdl_node)?; 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()); self.assert_no_mixed_children_and_properties(kdl_node)?; Ok(FloatingPanesLayout { name, height, width, x, y, run, focus, ..Default::default() }) } fn insert_children_to_pane_template( &self, kdl_node: &KdlNode, 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![]), }; 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_template, pane_template_kdl_node)?; self.insert_layout_children_or_error( pane_template, child_panes_layout, pane_template_kdl_node, )?; } Ok(()) } fn parse_pane_node_with_template( &self, kdl_node: &KdlNode, pane_template: PaneOrFloatingPane, pane_template_kdl_node: &KdlNode, ) -> Result { match pane_template { PaneOrFloatingPane::Pane(mut pane_template) | PaneOrFloatingPane::Either(mut pane_template) => { 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 close_on_exit = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "close_on_exit"); let start_suspended = 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)?; self.assert_no_bare_attributes_in_pane_node_with_template( &run, &pane_template.run, &args, &close_on_exit, &start_suspended, 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); if let Some(pane_template_run_command) = pane_template.run.as_mut() { // we need to do this because panes consuming a pane_template // can have bare args without a command pane_template_run_command.add_args(args); pane_template_run_command.add_close_on_exit(close_on_exit); pane_template_run_command.add_start_suspended(start_suspended); }; if let Some(borderless) = borderless { pane_template.borderless = borderless; } if let Some(focus) = focus { pane_template.focus = Some(focus); } if let Some(name) = name { pane_template.name = Some(name); } if let Some(split_size) = split_size { pane_template.split_size = Some(split_size); } if let Some(index_of_children) = pane_template.external_children_index { pane_template .children .insert(index_of_children, PaneLayout::default()); } pane_template.external_children_index = None; Ok(pane_template) }, PaneOrFloatingPane::FloatingPane(_) => { let pane_template_name = kdl_get_string_property_or_child_value_with_error!( pane_template_kdl_node, "name" ) .map(|name| name.to_string()); Err(ConfigError::new_layout_kdl_error( format!("pane_template {}, is a floating pane template (derived from its properties) and cannot be applied to a tiled pane", pane_template_name.unwrap_or("".into())), kdl_node.span().offset(), kdl_node.span().len(), )) }, } } fn parse_floating_pane_node_with_template( &self, kdl_node: &KdlNode, pane_template: PaneOrFloatingPane, pane_template_kdl_node: &KdlNode, ) -> Result { match pane_template { PaneOrFloatingPane::Pane(_) => { let pane_template_name = kdl_get_string_property_or_child_value_with_error!( pane_template_kdl_node, "name" ) .map(|name| name.to_string()); Err(ConfigError::new_layout_kdl_error( format!("pane_template {}, is a non-floating pane template (derived from its properties) and cannot be applied to a floating pane", pane_template_name.unwrap_or("".into())), kdl_node.span().offset(), kdl_node.span().len(), )) }, PaneOrFloatingPane::FloatingPane(mut pane_template) => { 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 close_on_exit = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "close_on_exit"); let start_suspended = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "start_suspended"); let run = self.parse_command_plugin_or_edit_block_for_template(kdl_node)?; self.assert_no_bare_attributes_in_pane_node_with_template( &run, &pane_template.run, &args, &close_on_exit, &start_suspended, kdl_node, )?; pane_template.run = Run::merge(&pane_template.run, &run); if let Some(pane_template_run_command) = pane_template.run.as_mut() { // we need to do this because panes consuming a pane_template // can have bare args without a command pane_template_run_command.add_args(args); pane_template_run_command.add_close_on_exit(close_on_exit); pane_template_run_command.add_start_suspended(start_suspended); }; if let Some(focus) = focus { pane_template.focus = Some(focus); } if let Some(name) = name { pane_template.name = Some(name); } let height = self.parse_percent_or_fixed(kdl_node, "height", false)?; 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); if let Some(height) = height { pane_template.height = Some(height); } if let Some(width) = width { pane_template.width = Some(width); } if let Some(y) = y { pane_template.y = Some(y); } if let Some(x) = x { pane_template.x = Some(x); } Ok(pane_template) }, PaneOrFloatingPane::Either(mut pane_template) => { 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 close_on_exit = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "close_on_exit"); let start_suspended = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "start_suspended"); let run = self.parse_command_plugin_or_edit_block_for_template(kdl_node)?; self.assert_no_bare_attributes_in_pane_node_with_template( &run, &pane_template.run, &args, &close_on_exit, &start_suspended, kdl_node, )?; pane_template.run = Run::merge(&pane_template.run, &run); if let Some(pane_template_run_command) = pane_template.run.as_mut() { // we need to do this because panes consuming a pane_template // can have bare args without a command pane_template_run_command.add_args(args); pane_template_run_command.add_close_on_exit(close_on_exit); pane_template_run_command.add_start_suspended(start_suspended); }; if let Some(focus) = focus { pane_template.focus = Some(focus); } if let Some(name) = name { pane_template.name = Some(name); } let height = self.parse_percent_or_fixed(kdl_node, "height", false)?; 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); if let Some(height) = height { floating_pane.height = Some(height); } if let Some(width) = width { floating_pane.width = Some(width); } if let Some(y) = y { floating_pane.y = Some(y); } if let Some(x) = x { floating_pane.x = Some(x); } Ok(floating_pane) }, } } fn parse_split_direction(&self, kdl_node: &KdlNode) -> Result { 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 has_only_neutral_pane_template_properties( &self, kdl_node: &KdlNode, ) -> Result { // pane properties let borderless = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "borderless"); let split_size = self.parse_split_size(kdl_node)?; let split_direction = kdl_get_string_property_or_child_value_with_error!(kdl_node, "split_direction"); let has_children_nodes = self.has_child_nodes(kdl_node); // floating pane properties let height = self.parse_percent_or_fixed(kdl_node, "height", false)?; 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 has_pane_properties = borderless.is_some() || split_size.is_some() || split_direction.is_some() || has_children_nodes; let has_floating_pane_properties = height.is_some() || width.is_some() || x.is_some() || y.is_some(); if has_pane_properties || has_floating_pane_properties { Ok(false) } else { Ok(true) } } fn differentiate_pane_and_floating_pane_template( &self, kdl_node: &KdlNode, ) -> Result { // returns true if it's a floating_pane template, false if not // pane properties let borderless = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "borderless"); let split_size = self.parse_split_size(kdl_node)?; let split_direction = kdl_get_string_property_or_child_value_with_error!(kdl_node, "split_direction"); let has_children_nodes = self.has_child_nodes(kdl_node); // floating pane properties let height = self.parse_percent_or_fixed(kdl_node, "height", false)?; 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 has_pane_properties = borderless.is_some() || split_size.is_some() || split_direction.is_some() || has_children_nodes; let has_floating_pane_properties = height.is_some() || width.is_some() || x.is_some() || y.is_some(); if has_pane_properties && has_floating_pane_properties { let mut pane_properties = vec![]; if borderless.is_some() { pane_properties.push("borderless"); } if split_size.is_some() { pane_properties.push("split_size"); } if split_direction.is_some() { pane_properties.push("split_direction"); } if has_children_nodes { pane_properties.push("child nodes"); } let mut floating_pane_properties = vec![]; if height.is_some() { floating_pane_properties.push("height"); } if width.is_some() { floating_pane_properties.push("width"); } if x.is_some() { floating_pane_properties.push("x"); } if y.is_some() { floating_pane_properties.push("y"); } Err(ConfigError::new_layout_kdl_error( format!( "A pane_template cannot have both pane ({}) and floating pane ({}) properties", pane_properties.join(", "), floating_pane_properties.join(", ") ), kdl_node.span().offset(), kdl_node.span().len(), )) } else if has_floating_pane_properties { Ok(true) } else { Ok(false) } } fn parse_pane_template_node(&mut self, kdl_node: &KdlNode) -> Result<(), ConfigError> { let template_name = kdl_get_string_property_or_child_value!(kdl_node, "name") .map(|s| s.to_string()) .ok_or(ConfigError::new_layout_kdl_error( "Pane templates must have a name".into(), kdl_node.span().offset(), kdl_node.span().len(), ))?; self.assert_legal_node_name(&template_name, kdl_node)?; self.assert_legal_template_name(&template_name, kdl_node)?; let focus = kdl_get_bool_property_or_child_value_with_error!(kdl_node, "focus"); let run = self.parse_command_plugin_or_edit_block(kdl_node)?; let is_floating = self.differentiate_pane_and_floating_pane_template(&kdl_node)?; let can_be_either_floating_or_tiled = self.has_only_neutral_pane_template_properties(&kdl_node)?; if can_be_either_floating_or_tiled { self.assert_valid_pane_or_floating_pane_properties(kdl_node)?; self.pane_templates.insert( template_name, ( PaneOrFloatingPane::Either(PaneLayout { focus, run, ..Default::default() }), kdl_node.clone(), ), ); } else if is_floating { self.assert_valid_floating_pane_properties(kdl_node)?; // floating pane properties let height = self.parse_percent_or_fixed(kdl_node, "height", false)?; 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)?; self.pane_templates.insert( template_name, ( PaneOrFloatingPane::FloatingPane(FloatingPanesLayout { focus, run, height, width, x, y, ..Default::default() }), kdl_node.clone(), ), ); } else { self.assert_valid_pane_properties(kdl_node)?; // pane properties let borderless = 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![]), }; self.assert_no_mixed_children_and_properties(kdl_node)?; self.pane_templates.insert( template_name, ( PaneOrFloatingPane::Pane(PaneLayout { borderless: borderless.unwrap_or_default(), focus, split_size, run, children_split_direction, external_children_index, children: pane_parts, ..Default::default() }), kdl_node.clone(), ), ); } Ok(()) } fn parse_tab_node( &mut self, kdl_node: &KdlNode, ) -> Result<(bool, Option, PaneLayout, Vec), ConfigError> { // (is_focused, Option, PaneLayout, Vec) 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()); let tab_cwd = kdl_get_string_property_or_child_value!(kdl_node, "cwd").map(|c| PathBuf::from(c)); let is_focused = kdl_get_bool_property_or_child_value!(kdl_node, "focus").unwrap_or(false); let children_split_direction = self.parse_split_direction(kdl_node)?; 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)? }, None => vec![], }; let mut pane_layout = PaneLayout { children_split_direction, children, ..Default::default() }; if let Some(cwd_prefix) = &self.cwd_prefix(tab_cwd.as_ref())? { pane_layout.add_cwd_to_layout(&cwd_prefix); } Ok((is_focused, tab_name, pane_layout, child_floating_panes)) } fn parse_child_pane_nodes_for_tab( &self, children: &[KdlNode], child_floating_panes: &mut Vec, ) -> Result, ConfigError> { let mut nodes = vec![]; for child in children { if kdl_name!(child) == "pane" { nodes.push(self.parse_pane_node(child)?); } else if let Some((pane_template, pane_template_kdl_node)) = self.pane_templates.get(kdl_name!(child)).cloned() { nodes.push(self.parse_pane_node_with_template( child, pane_template, &pane_template_kdl_node, )?); } else if kdl_name!(child) == "floating_panes" { self.populate_floating_pane_children(child, child_floating_panes)?; } else if self.is_a_valid_tab_property(kdl_name!(child)) { return Err(ConfigError::new_layout_kdl_error( format!("Tab property '{}' must be placed on the tab title line and not in the child braces", kdl_name!(child)), child.span().offset(), child.span().len() )); } else { return Err(ConfigError::new_layout_kdl_error( format!("Invalid tab property: {}", kdl_name!(child)), child.span().offset(), child.span().len(), )); } } if nodes.is_empty() { nodes.push(PaneLayout::default()); } Ok(nodes) } fn parse_child_pane_nodes_for_pane( &self, children: &[KdlNode], ) -> Result<(Option, Vec), ConfigError> { // usize is external_children_index let mut external_children_index = None; 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(), )); } external_children_index = Some(i); } else if let Some((pane_template, pane_template_kdl_node)) = self.pane_templates.get(kdl_name!(child)).cloned() { nodes.push(self.parse_pane_node_with_template( child, pane_template, &pane_template_kdl_node, )?); } else if !self.is_a_valid_pane_property(kdl_name!(child)) { return Err(ConfigError::new_layout_kdl_error( format!("Unknown pane property: {}", kdl_name!(child)), child.span().offset(), child.span().len(), )); } } Ok((external_children_index, nodes)) } fn has_child_nodes(&self, kdl_node: &KdlNode) -> bool { if let Some(children) = kdl_children_nodes!(kdl_node) { for child in children { if kdl_name!(child) == "pane" || kdl_name!(child) == "children" || self.pane_templates.get(kdl_name!(child)).is_some() { return true; } } } return false; } fn has_child_panes_tabs_or_templates(&self, kdl_node: &KdlNode) -> bool { if let Some(children) = kdl_children_nodes!(kdl_node) { for child in children { let child_node_name = kdl_name!(child); if child_node_name == "pane" || child_node_name == "children" || child_node_name == "tab" || child_node_name == "children" { return true; } else if let Some((_pane_template, _pane_template_kdl_node)) = self.pane_templates.get(child_node_name).cloned() { return true; } } } false } fn assert_no_bare_attributes_in_pane_node_with_template( &self, pane_run: &Option, pane_template_run: &Option, args: &Option>, close_on_exit: &Option, start_suspended: &Option, 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 )); } if let (None, None, true) = (pane_run, pane_template_run, close_on_exit.is_some()) { return Err(kdl_parsing_error!( format!("close_on_exit can only be specified if a command was specified either in the pane_template or in the pane"), pane_node )); } if let (None, None, true) = (pane_run, pane_template_run, start_suspended.is_some()) { return Err(kdl_parsing_error!( format!("start_suspended can only be specified if a command was specified either in the pane_template or in the pane"), pane_node )); } Ok(()) } fn assert_no_bare_attributes_in_pane_node( &self, command: &Option, args: &Option>, close_on_exit: &Option, start_suspended: &Option, pane_node: &KdlNode, ) -> Result<(), ConfigError> { if command.is_none() { if close_on_exit.is_some() { return Err(ConfigError::new_layout_kdl_error( "close_on_exit can only be set if a command was specified".into(), pane_node.span().offset(), pane_node.span().len(), )); } if start_suspended.is_some() { return Err(ConfigError::new_layout_kdl_error( "start_suspended can only be set if a command was specified".into(), pane_node.span().offset(), pane_node.span().len(), )); } if args.is_some() { return Err(ConfigError::new_layout_kdl_error( "args can only be set if a command was specified".into(), pane_node.span().offset(), pane_node.span().len(), )); } } Ok(()) } fn assert_one_children_block( &self, layout: &PaneLayout, kdl_node: &KdlNode, ) -> Result<(), ConfigError> { let children_block_count = layout.children_block_count(); if children_block_count != 1 { return Err(ConfigError::new_layout_kdl_error(format!("This template has {} children blocks, only 1 is allowed when used to insert child panes", children_block_count), kdl_node.span().offset(), kdl_node.span().len())); } Ok(()) } fn assert_valid_pane_properties(&self, pane_node: &KdlNode) -> Result<(), ConfigError> { for entry in pane_node.entries() { match entry .name() .map(|e| e.value()) .or_else(|| entry.value().as_string()) { Some(string_name) => { if !self.is_a_valid_pane_property(string_name) { return Err(ConfigError::new_layout_kdl_error( format!("Unknown pane property: {}", string_name), entry.span().offset(), entry.span().len(), )); } }, None => { return Err(ConfigError::new_layout_kdl_error( "Unknown pane property".into(), entry.span().offset(), entry.span().len(), )); }, } } Ok(()) } fn assert_valid_floating_pane_properties( &self, pane_node: &KdlNode, ) -> Result<(), ConfigError> { for entry in pane_node.entries() { match entry .name() .map(|e| e.value()) .or_else(|| entry.value().as_string()) { Some(string_name) => { if !self.is_a_valid_floating_pane_property(string_name) { return Err(ConfigError::new_layout_kdl_error( format!("Unknown floating pane property: {}", string_name), entry.span().offset(), entry.span().len(), )); } }, None => { return Err(ConfigError::new_layout_kdl_error( "Unknown floating pane property".into(), entry.span().offset(), entry.span().len(), )); }, } } Ok(()) } fn assert_valid_pane_or_floating_pane_properties( &self, pane_node: &KdlNode, ) -> Result<(), ConfigError> { for entry in pane_node.entries() { match entry .name() .map(|e| e.value()) .or_else(|| entry.value().as_string()) { Some(string_name) => { if !self.is_a_valid_floating_pane_property(string_name) || !self.is_a_valid_pane_property(string_name) { return Err(ConfigError::new_layout_kdl_error( format!("Unknown pane property: {}", string_name), entry.span().offset(), entry.span().len(), )); } }, None => { return Err(ConfigError::new_layout_kdl_error( "Unknown pane property".into(), entry.span().offset(), entry.span().len(), )); }, } } Ok(()) } fn assert_valid_tab_properties(&self, pane_node: &KdlNode) -> Result<(), ConfigError> { let all_property_names = kdl_property_names!(pane_node); for name in all_property_names { if !self.is_a_valid_tab_property(name) { return Err(ConfigError::new_layout_kdl_error( format!("Invalid tab property '{}'", name), pane_node.span().offset(), pane_node.span().len(), )); } } Ok(()) } fn assert_no_mixed_children_and_properties( &self, kdl_node: &KdlNode, ) -> 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 .parse_command_plugin_or_edit_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_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"); } if has_cwd_prop { offending_nodes.push("cwd"); } Err(ConfigError::new_layout_kdl_error( format!( "Cannot have both properties ({}) and nested children", offending_nodes.join(", ") ), kdl_node.span().offset(), kdl_node.span().len(), )) } else { Ok(()) } } fn insert_layout_children_or_error( &self, layout: &mut PaneLayout, mut child_panes_layout: PaneLayout, kdl_node: &KdlNode, ) -> Result<(), ConfigError> { let successfully_inserted = layout.insert_children_layout(&mut child_panes_layout)?; if !successfully_inserted { Err(ConfigError::new_layout_kdl_error( "This template does not have children".into(), kdl_node.span().offset(), kdl_node.span().len(), )) } else { Ok(()) } } fn parse_tab_node_with_template( &self, kdl_node: &KdlNode, mut tab_layout: PaneLayout, mut tab_template_floating_panes: Vec, tab_layout_kdl_node: &KdlNode, ) -> Result<(bool, Option, PaneLayout, Vec), ConfigError> { // (is_focused, Option, PaneLayout, Vec) let tab_name = kdl_get_string_property_or_child_value!(kdl_node, "name").map(|s| s.to_string()); let tab_cwd = kdl_get_string_property_or_child_value!(kdl_node, "cwd").map(|c| PathBuf::from(c)); let is_focused = kdl_get_bool_property_or_child_value!(kdl_node, "focus").unwrap_or(false); 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 { children_split_direction, children: child_panes, ..Default::default() }; self.assert_one_children_block(&tab_layout, &tab_layout_kdl_node)?; self.insert_layout_children_or_error( &mut tab_layout, child_panes_layout, &tab_layout_kdl_node, )?; }, None => { if let Some(index_of_children) = tab_layout.external_children_index { tab_layout .children .insert(index_of_children, PaneLayout::default()); } }, } if let Some(cwd_prefix) = self.cwd_prefix(tab_cwd.as_ref())? { tab_layout.add_cwd_to_layout(&cwd_prefix); } tab_layout.external_children_index = None; Ok(( is_focused, tab_name, tab_layout, tab_template_floating_panes, )) } fn populate_one_tab_template(&mut self, kdl_node: &KdlNode) -> Result<(), ConfigError> { let template_name = kdl_get_string_property_or_child_value_with_error!(kdl_node, "name") .map(|s| s.to_string()) .ok_or(ConfigError::new_layout_kdl_error( "Tab templates must have a name".into(), kdl_node.span().offset(), kdl_node.span().len(), ))?; self.assert_legal_node_name(&template_name, kdl_node)?; self.assert_legal_template_name(&template_name, kdl_node)?; if self.tab_templates.contains_key(&template_name) { return Err(ConfigError::new_layout_kdl_error( format!( "Duplicate definition of the \"{}\" tab_template", template_name ), kdl_node.span().offset(), kdl_node.span().len(), )); } if self.pane_templates.contains_key(&template_name) { return Err(ConfigError::new_layout_kdl_error( format!("There is already a pane_template with the name \"{}\" - can't have a tab_template with the same name", template_name), kdl_node.span().offset(), kdl_node.span().len(), )); } let (tab_template, tab_template_floating_panes) = self.parse_tab_template_node(kdl_node)?; self.tab_templates.insert( template_name, (tab_template, tab_template_floating_panes, kdl_node.clone()), ); Ok(()) } fn populate_default_tab_template(&mut self, kdl_node: &KdlNode) -> Result<(), ConfigError> { let (tab_template, tab_template_floating_panes) = self.parse_tab_template_node(kdl_node)?; self.default_tab_template = Some((tab_template, tab_template_floating_panes, kdl_node.clone())); Ok(()) } fn parse_tab_template_node( &self, kdl_node: &KdlNode, ) -> Result<(PaneLayout, Vec), ConfigError> { self.assert_valid_tab_properties(kdl_node)?; let children_split_direction = self.parse_split_direction(kdl_node)?; let mut tab_children = vec![]; let mut tab_floating_children = vec![]; let mut external_children_index = None; let mut children_index_offset = 0; if let Some(children) = kdl_children_nodes!(kdl_node) { for (i, child) in children.iter().enumerate() { 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_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(), )); } external_children_index = Some(i.saturating_sub(children_index_offset)); } else if let Some((pane_template, pane_template_kdl_node)) = self.pane_templates.get(kdl_name!(child)).cloned() { tab_children.push(self.parse_pane_node_with_template( child, pane_template, &pane_template_kdl_node, )?); } else if kdl_name!(child) == "floating_panes" { children_index_offset += 1; self.populate_floating_pane_children(child, &mut tab_floating_children)?; } else if self.is_a_valid_tab_property(kdl_name!(child)) { return Err(ConfigError::new_layout_kdl_error( format!("Tab property '{}' must be placed on the tab_template title line and not in the child braces", kdl_name!(child)), child.span().offset(), child.span().len() )); } else { return Err(ConfigError::new_layout_kdl_error( format!("Invalid tab_template property: {}", kdl_name!(child)), child.span().offset(), child.span().len(), )); } } } Ok(( PaneLayout { children_split_direction, children: tab_children, external_children_index, ..Default::default() }, tab_floating_children, )) } fn default_template(&self) -> Result, 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()) } template.external_children_index = None; Ok(Some(template)) }, None => Ok(None), } } pub fn get_pane_template_dependency_tree( &self, kdl_children: &'a [KdlNode], ) -> Result>, ConfigError> { let mut dependency_tree = HashMap::new(); for child in kdl_children { if kdl_name!(child) == "pane_template" { let template_name = kdl_get_string_property_or_child_value!(child, "name").ok_or( ConfigError::new_layout_kdl_error( "Pane templates must have a name".into(), child.span().offset(), child.span().len(), ), )?; let mut template_children = HashSet::new(); self.get_pane_template_dependencies(child, &mut template_children)?; if dependency_tree.contains_key(template_name) { return Err(ConfigError::new_layout_kdl_error( format!( "Duplicate definition of the \"{}\" pane_template", template_name ), child.span().offset(), child.span().len(), )); } dependency_tree.insert(template_name, template_children); } } let all_pane_template_names: HashSet<&str> = dependency_tree.keys().cloned().collect(); for (_pane_template_name, dependencies) in dependency_tree.iter_mut() { dependencies.retain(|d| all_pane_template_names.contains(d)); } Ok(dependency_tree) } fn get_pane_template_dependencies( &self, node: &'a KdlNode, all_dependencies: &mut HashSet<&'a str>, ) -> Result<(), ConfigError> { if let Some(children) = kdl_children_nodes!(node) { for child in children { let child_name = kdl_name!(child); if child_name == "pane" { self.get_pane_template_dependencies(child, all_dependencies)?; } else if !self.is_a_reserved_word(child_name) { all_dependencies.insert(child_name); self.get_pane_template_dependencies(child, all_dependencies)?; } } } Ok(()) } pub fn parse_pane_template_by_name( &mut self, pane_template_name: &str, kdl_children: &[KdlNode], ) -> Result<(), ConfigError> { for child in kdl_children.iter() { let child_name = kdl_name!(child); if child_name == "pane_template" { let child_name = kdl_get_string_property_or_child_value!(child, "name"); if child_name == Some(pane_template_name) { self.parse_pane_template_node(child)?; } } } 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], kdl_layout: &KdlDocument, ) -> Result<(), ConfigError> { let mut pane_template_dependency_tree = self.get_pane_template_dependency_tree(layout_children)?; let mut pane_template_names_to_parse: Vec<&str> = vec![]; // toposort the dependency tree so that we parse the pane_templates before their // dependencies while !pane_template_dependency_tree.is_empty() { let mut candidates: Vec<&str> = vec![]; for (pane_tempalte, dependencies) in pane_template_dependency_tree.iter() { if dependencies.is_empty() { candidates.push(pane_tempalte); } } if candidates.is_empty() { return Err(ConfigError::new_layout_kdl_error( "Circular dependency detected between pane templates.".into(), kdl_layout.span().offset(), kdl_layout.span().len(), )); } for candidate_to_remove in candidates { pane_template_dependency_tree.remove(candidate_to_remove); for (_pane_tempalte, dependencies) in pane_template_dependency_tree.iter_mut() { dependencies.remove(candidate_to_remove); } pane_template_names_to_parse.push(candidate_to_remove); } } // once we've toposorted, parse the sorted list in order for pane_template_name in pane_template_names_to_parse { self.parse_pane_template_by_name(pane_template_name, &layout_children)?; } Ok(()) } fn populate_tab_templates(&mut self, layout_children: &[KdlNode]) -> Result<(), ConfigError> { for child in layout_children.iter() { let child_name = kdl_name!(child); if child_name == "tab_template" { self.populate_one_tab_template(child)?; } else if child_name == "default_tab_template" { self.populate_default_tab_template(child)?; } } Ok(()) } fn layout_with_tabs( &self, tabs: Vec<(Option, PaneLayout, Vec)>, focused_tab_index: Option, ) -> Result { let template = self .default_template()? .unwrap_or_else(|| PaneLayout::default()); Ok(Layout { tabs: tabs, template: Some(template), focused_tab_index, ..Default::default() }) } fn layout_with_one_tab( &self, panes: Vec, floating_panes: Vec, ) -> Result { let main_tab_layout = PaneLayout { children: panes, ..Default::default() }; let default_template = self.default_template()?; let tabs = if default_template.is_none() { // in this case, the layout will be created as the default template and we don't need // to explicitly place it in the first tab vec![] } else { vec![(None, main_tab_layout.clone(), floating_panes.clone())] }; let template = default_template.unwrap_or_else(|| main_tab_layout.clone()); // create a layout with one tab that has these child panes Ok(Layout { tabs, template: Some(template), floating_panes_template: floating_panes, ..Default::default() }) } fn layout_with_one_pane( &self, child_floating_panes: Vec, ) -> Result { let template = self .default_template()? .unwrap_or_else(|| PaneLayout::default()); Ok(Layout { template: Some(template), floating_panes_template: child_floating_panes, ..Default::default() }) } fn populate_layout_child( &mut self, child: &KdlNode, child_tabs: &mut Vec<(bool, Option, PaneLayout, Vec)>, child_panes: &mut Vec, child_floating_panes: &mut Vec, ) -> Result<(), ConfigError> { let child_name = kdl_name!(child); if (child_name == "pane" || child_name == "floating_panes") && !child_tabs.is_empty() { return Err(ConfigError::new_layout_kdl_error( "Cannot have both tabs and panes in the same node".into(), child.span().offset(), child.span().len(), )); } if child_name == "pane" { let mut pane_node = self.parse_pane_node(child)?; if let Some(global_cwd) = &self.global_cwd { pane_node.add_cwd_to_layout(&global_cwd); } child_panes.push(pane_node); } else if child_name == "floating_panes" { self.populate_floating_pane_children(child, child_floating_panes)?; } else if child_name == "tab" { if !child_panes.is_empty() || !child_floating_panes.is_empty() { return Err(ConfigError::new_layout_kdl_error( "Cannot have both tabs and panes in the same node".into(), child.span().offset(), child.span().len(), )); } 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(); child_tabs.push(self.parse_tab_node_with_template( child, default_tab_template, default_tab_template_floating_panes.clone(), default_tab_template_kdl_node, )?); }, None => { child_tabs.push(self.parse_tab_node(child)?); }, } } else if let Some((tab_template, tab_template_floating_panes, tab_template_kdl_node)) = self.tab_templates.get(child_name).cloned() { if !child_panes.is_empty() { return Err(ConfigError::new_layout_kdl_error( "Cannot have both tabs and panes in the same node".into(), child.span().offset(), child.span().len(), )); } child_tabs.push(self.parse_tab_node_with_template( child, tab_template, tab_template_floating_panes, &tab_template_kdl_node, )?); } else if let Some((pane_template, pane_template_kdl_node)) = self.pane_templates.get(child_name).cloned() { if !child_tabs.is_empty() { return Err(ConfigError::new_layout_kdl_error( "Cannot have both tabs and panes in the same node".into(), child.span().offset(), child.span().len(), )); } let mut pane_template = self.parse_pane_node_with_template(child, pane_template, &pane_template_kdl_node)?; if let Some(cwd_prefix) = &self.cwd_prefix(None)? { pane_template.add_cwd_to_layout(&cwd_prefix); } child_panes.push(pane_template); } else if !self.is_a_reserved_word(child_name) { return Err(ConfigError::new_layout_kdl_error( format!("Unknown layout node: '{}'", child_name), child.span().offset(), child.span().len(), )); } Ok(()) } fn populate_floating_pane_children( &self, child: &KdlNode, child_floating_panes: &mut Vec, ) -> Result<(), ConfigError> { if let Some(children) = kdl_children_nodes!(child) { for child in children { if kdl_name!(child) == "pane" { let mut pane_node = self.parse_floating_pane_node(child)?; if let Some(global_cwd) = &self.global_cwd { pane_node.add_cwd_to_layout(&global_cwd); } child_floating_panes.push(pane_node); } else if let Some((pane_template, pane_template_kdl_node)) = self.pane_templates.get(kdl_name!(child)).cloned() { let pane_node = self.parse_floating_pane_node_with_template( child, pane_template, &pane_template_kdl_node, )?; child_floating_panes.push(pane_node); } else { // TODO: invalid node name return Err(ConfigError::new_layout_kdl_error( format!( "floating_panes can only contain pane nodes, found: {}", kdl_name!(child) ), child.span().offset(), child.span().len(), )); } } }; Ok(()) } pub fn parse(&mut self) -> Result { let kdl_layout: KdlDocument = self.raw_layout.parse()?; let layout_node = kdl_layout .nodes() .iter() .find(|n| kdl_name!(n) == "layout") .ok_or(ConfigError::new_layout_kdl_error( "No layout found".into(), kdl_layout.span().offset(), kdl_layout.span().len(), ))?; let has_multiple_layout_nodes = kdl_layout .nodes() .iter() .filter(|n| kdl_name!(n) == "layout") .count() > 1; if has_multiple_layout_nodes { return Err(ConfigError::new_layout_kdl_error( "Only one layout node per file allowed".into(), kdl_layout.span().offset(), kdl_layout.span().len(), )); } let mut child_tabs = vec![]; let mut child_panes = vec![]; let mut child_floating_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 { self.populate_layout_child( child, &mut child_tabs, &mut child_panes, &mut child_floating_panes, )?; } } if !child_tabs.is_empty() { let has_more_than_one_focused_tab = child_tabs .iter() .filter(|(is_focused, _, _, _)| *is_focused) .count() > 1; if has_more_than_one_focused_tab { return Err(ConfigError::new_layout_kdl_error( "Only one tab can be focused".into(), kdl_layout.span().offset(), kdl_layout.span().len(), )); } let focused_tab_index = child_tabs .iter() .position(|(is_focused, _, _, _)| *is_focused); let child_tabs: Vec<(Option, PaneLayout, Vec)> = child_tabs .drain(..) .map( |(_is_focused, tab_name, pane_layout, floating_panes_layout)| { (tab_name, pane_layout, floating_panes_layout) }, ) .collect(); self.layout_with_tabs(child_tabs, focused_tab_index) } else if !child_panes.is_empty() { self.layout_with_one_tab(child_panes, child_floating_panes) } else { self.layout_with_one_pane(child_floating_panes) } } }