diff --git a/zellij-utils/src/session_serialization.rs b/zellij-utils/src/session_serialization.rs index 78900c03..1586e6cd 100644 --- a/zellij-utils/src/session_serialization.rs +++ b/zellij-utils/src/session_serialization.rs @@ -1,31 +1,18 @@ +use kdl::{KdlDocument, KdlEntry, KdlNode, KdlValue}; use std::collections::BTreeMap; use std::path::PathBuf; use crate::{ + input::command::RunCommand, input::layout::PluginUserConfiguration, input::layout::{ - FloatingPaneLayout, Layout, PercentOrFixed, Run, RunPluginOrAlias, SplitDirection, - SplitSize, SwapFloatingLayout, SwapTiledLayout, TiledPaneLayout, + FloatingPaneLayout, Layout, LayoutConstraint, PercentOrFixed, Run, RunPlugin, + RunPluginOrAlias, SplitDirection, SplitSize, SwapFloatingLayout, SwapTiledLayout, + TiledPaneLayout, }, pane_size::{Constraint, PaneGeom}, }; -const INDENT: &str = " "; -const DOUBLE_INDENT: &str = " "; -const TRIPLE_INDENT: &str = " "; - -fn indent(s: &str, prefix: &str) -> String { - let mut result = String::new(); - for line in s.lines() { - if line.chars().any(|c| !c.is_whitespace()) { - result.push_str(prefix); - result.push_str(line); - } - result.push('\n'); - } - result -} - #[derive(Default, Debug, Clone)] pub struct GlobalLayoutManifest { pub global_cwd: Option, @@ -57,44 +44,54 @@ pub fn serialize_session_layout( global_layout_manifest: GlobalLayoutManifest, ) -> Result<(String, BTreeMap), &'static str> { // BTreeMap is the pane contents and their file names - let mut kdl_string = String::from("layout {\n"); + let mut document = KdlDocument::new(); let mut pane_contents = BTreeMap::new(); - stringify_global_cwd(&global_layout_manifest.global_cwd, &mut kdl_string); - if let Err(e) = stringify_multiple_tabs( - global_layout_manifest.tabs, - &mut pane_contents, - &mut kdl_string, - ) { - return Err(e); + let mut layout_node = KdlNode::new("layout"); + let mut layout_node_children = KdlDocument::new(); + if let Some(global_cwd) = serialize_global_cwd(&global_layout_manifest.global_cwd) { + layout_node_children.nodes_mut().push(global_cwd); } - stringify_new_tab_template( + match serialize_multiple_tabs(global_layout_manifest.tabs, &mut pane_contents) { + Ok(mut serialized_tabs) => { + layout_node_children + .nodes_mut() + .append(&mut serialized_tabs); + }, + Err(e) => { + return Err(e); + }, + } + serialize_new_tab_template( global_layout_manifest.default_layout.template, &mut pane_contents, - &mut kdl_string, + &mut layout_node_children, ); - stringify_swap_tiled_layouts( + serialize_swap_tiled_layouts( global_layout_manifest.default_layout.swap_tiled_layouts, &mut pane_contents, - &mut kdl_string, + &mut layout_node_children, ); - stringify_swap_floating_layouts( + serialize_swap_floating_layouts( global_layout_manifest.default_layout.swap_floating_layouts, &mut pane_contents, - &mut kdl_string, + &mut layout_node_children, ); - kdl_string.push_str("}"); - Ok((kdl_string, pane_contents)) + + layout_node.set_children(layout_node_children); + document.nodes_mut().push(layout_node); + Ok((document.to_string(), pane_contents)) } -fn stringify_tab( +fn serialize_tab( tab_name: String, is_focused: bool, hide_floating_panes: bool, tiled_panes: &Vec, floating_panes: &Vec, pane_contents: &mut BTreeMap, -) -> Option { - let mut kdl_string = String::new(); +) -> Option { + let mut serialized_tab = KdlNode::new("tab"); + let mut serialized_tab_children = KdlDocument::new(); match get_tiled_panes_layout_from_panegeoms(tiled_panes, None) { Some(tiled_panes_layout) => { let floating_panes_layout = get_floating_panes_layout_from_panegeoms(floating_panes); @@ -106,21 +103,30 @@ fn stringify_tab( } else { tiled_panes_layout.children }; - let mut tab_attributes = vec![format!("name=\"{}\"", tab_name,)]; + serialized_tab + .entries_mut() + .push(KdlEntry::new_prop("name", tab_name)); if is_focused { - tab_attributes.push(format!("focus=true")); + serialized_tab + .entries_mut() + .push(KdlEntry::new_prop("focus", KdlValue::Bool(true))); } if hide_floating_panes { - tab_attributes.push(format!("hide_floating_panes=true")); + serialized_tab.entries_mut().push(KdlEntry::new_prop( + "hide_floating_panes", + KdlValue::Bool(true), + )); } - kdl_string.push_str(&kdl_string_from_tab( + + serialize_tiled_and_floating_panes( &tiled_panes, - &floating_panes_layout, - tab_attributes, - None, + floating_panes_layout, pane_contents, - )); - Some(kdl_string) + &mut serialized_tab_children, + ); + + serialized_tab.set_children(serialized_tab_children); + Some(serialized_tab) }, None => { return None; @@ -128,54 +134,44 @@ fn stringify_tab( } } -/// Redundant with `geoms_to_kdl_tab` -fn kdl_string_from_tab( +fn serialize_tiled_and_floating_panes( tiled_panes: &Vec, - floating_panes: &Vec, - node_attributes: Vec, - node_name: Option, + floating_panes_layout: Vec, pane_contents: &mut BTreeMap, -) -> String { - let mut kdl_string = if node_attributes.is_empty() { - format!("{} {{\n", node_name.unwrap_or_else(|| "tab".to_owned())) - } else { - format!( - "{} {} {{\n", - node_name.unwrap_or_else(|| "tab".to_owned()), - node_attributes.join(" ") - ) - }; + serialized_tab_children: &mut KdlDocument, +) { for tiled_pane_layout in tiled_panes { let ignore_size = false; - let sub_kdl_string = - kdl_string_from_tiled_pane(&tiled_pane_layout, ignore_size, pane_contents); - kdl_string.push_str(&indent(&sub_kdl_string, INDENT)); + let tiled_pane_node = serialize_tiled_pane(tiled_pane_layout, ignore_size, pane_contents); + serialized_tab_children.nodes_mut().push(tiled_pane_node); } - if !floating_panes.is_empty() { - kdl_string.push_str(&indent("floating_panes {\n", INDENT)); - for floating_pane_layout in floating_panes { - let sub_kdl_string = - kdl_string_from_floating_pane(&floating_pane_layout, pane_contents); - kdl_string.push_str(&indent(&sub_kdl_string, DOUBLE_INDENT)); + if !floating_panes_layout.is_empty() { + let mut floating_panes_node = KdlNode::new("floating_panes"); + let mut floating_panes_node_children = KdlDocument::new(); + for floating_pane in floating_panes_layout { + let pane_node = serialize_floating_pane(&floating_pane, pane_contents); + floating_panes_node_children.nodes_mut().push(pane_node); } - kdl_string.push_str(&indent("}\n", INDENT)); + floating_panes_node.set_children(floating_panes_node_children); + serialized_tab_children + .nodes_mut() + .push(floating_panes_node); } - kdl_string.push_str("}\n"); - kdl_string } -/// Pane declaration and recursion -fn kdl_string_from_tiled_pane( +fn serialize_tiled_pane( layout: &TiledPaneLayout, ignore_size: bool, pane_contents: &mut BTreeMap, -) -> String { +) -> KdlNode { let (command, args) = extract_command_and_args(&layout.run); let (plugin, plugin_config) = extract_plugin_and_config(&layout.run); let (edit, _line_number) = extract_edit_and_line_number(&layout.run); let cwd = layout.run.as_ref().and_then(|r| r.get_cwd()); let has_children = layout.external_children_index.is_some() || !layout.children.is_empty(); - let mut kdl_string = stringify_pane_title_and_attributes( + + let mut tiled_pane_node = KdlNode::new("pane"); + serialize_pane_title_and_attributes( &command, &edit, &layout.name, @@ -184,36 +180,39 @@ fn kdl_string_from_tiled_pane( &layout.pane_initial_contents, pane_contents, has_children, + &mut tiled_pane_node, ); - stringify_tiled_layout_attributes(&layout, ignore_size, &mut kdl_string); + serialize_tiled_layout_attributes(&layout, ignore_size, &mut tiled_pane_node); let has_child_attributes = !layout.children.is_empty() || layout.external_children_index.is_some() || !args.is_empty() || plugin.is_some() || command.is_some(); if has_child_attributes { - kdl_string.push_str(" {\n"); - stringify_args(args, &mut kdl_string); - stringify_start_suspended(&command, &mut kdl_string); - stringify_plugin(plugin, plugin_config, &mut kdl_string); + let mut tiled_pane_node_children = KdlDocument::new(); + serialize_args(args, &mut tiled_pane_node_children); + serialize_start_suspended(&command, &mut tiled_pane_node_children); + serialize_plugin(plugin, plugin_config, &mut tiled_pane_node_children); if layout.children.is_empty() && layout.external_children_index.is_some() { - kdl_string.push_str(&indent(&"children\n", INDENT)); + tiled_pane_node_children + .nodes_mut() + .push(KdlNode::new("children")); } for (i, pane) in layout.children.iter().enumerate() { if Some(i) == layout.external_children_index { - kdl_string.push_str(&indent(&"children\n", INDENT)); + tiled_pane_node_children + .nodes_mut() + .push(KdlNode::new("children")); } else { let ignore_size = layout.children_are_stacked; - let sub_kdl_string = kdl_string_from_tiled_pane(&pane, ignore_size, pane_contents); - kdl_string.push_str(&indent(&sub_kdl_string, INDENT)); + let child_pane_node = serialize_tiled_pane(&pane, ignore_size, pane_contents); + tiled_pane_node_children.nodes_mut().push(child_pane_node); } } - kdl_string.push_str("}\n"); - } else { - kdl_string.push_str("\n"); + tiled_pane_node.set_children(tiled_pane_node_children); } - kdl_string + tiled_pane_node } pub fn extract_command_and_args(layout_run: &Option) -> (Option, Vec) { @@ -256,7 +255,7 @@ pub fn extract_edit_and_line_number(layout_run: &Option) -> (Option } } -fn stringify_pane_title_and_attributes( +fn serialize_pane_title_and_attributes( command: &Option, edit: &Option, name: &Option, @@ -265,162 +264,214 @@ fn stringify_pane_title_and_attributes( initial_pane_contents: &Option, pane_contents: &mut BTreeMap, has_children: bool, -) -> String { - let mut kdl_string = match (&command, &edit) { - (Some(command), _) => format!("pane command=\"{}\"", command), - (None, Some(edit)) => format!("pane edit=\"{}\"", edit), - (None, None) => format!("pane"), + kdl_node: &mut KdlNode, +) { + match (&command, &edit) { + (Some(command), _) => kdl_node + .entries_mut() + .push(KdlEntry::new_prop("command", command.to_owned())), + (None, Some(edit)) => kdl_node + .entries_mut() + .push(KdlEntry::new_prop("edit", edit.to_owned())), + _ => {}, }; if let Some(name) = name { - kdl_string.push_str(&format!(" name=\"{}\"", name)); + kdl_node + .entries_mut() + .push(KdlEntry::new_prop("name", name.to_owned())); } if let Some(cwd) = cwd { let path = cwd.display().to_string(); if !path.is_empty() && !has_children { - kdl_string.push_str(&format!(" cwd=\"{}\"", path)); + kdl_node + .entries_mut() + .push(KdlEntry::new_prop("cwd", path.to_owned())); } } if focus.unwrap_or(false) { - kdl_string.push_str(&" focus=true"); + kdl_node + .entries_mut() + .push(KdlEntry::new_prop("focus", KdlValue::Bool(true))); } if let Some(initial_pane_contents) = initial_pane_contents.as_ref() { if command.is_none() && edit.is_none() { let file_name = format!("initial_contents_{}", pane_contents.keys().len() + 1); - kdl_string.push_str(&format!(" contents_file=\"{}\"", file_name)); - pane_contents.insert(file_name.to_string(), initial_pane_contents.clone()); + kdl_node + .entries_mut() + .push(KdlEntry::new_prop("contents_file", file_name.clone())); + + pane_contents.insert(file_name, initial_pane_contents.clone()); } } - kdl_string } -fn stringify_args(args: Vec, kdl_string: &mut String) { +fn serialize_args(args: Vec, pane_node_children: &mut KdlDocument) { if !args.is_empty() { - let args = args - .iter() - .map(|a| format!("\"{}\"", a)) - .collect::>() - .join(" "); - kdl_string.push_str(&indent(&format!("args {}\n", args), INDENT)); + let mut args_node = KdlNode::new("args"); + for arg in &args { + args_node.entries_mut().push(KdlEntry::new(arg.to_owned())); + } + pane_node_children.nodes_mut().push(args_node); } } -fn stringify_plugin( +fn serialize_plugin( plugin: Option, plugin_config: Option, - kdl_string: &mut String, + pane_node_children: &mut KdlDocument, ) { if let Some(plugin) = plugin { + let mut plugin_node = KdlNode::new("plugin"); + plugin_node + .entries_mut() + .push(KdlEntry::new_prop("location", plugin.to_owned())); if let Some(plugin_config) = plugin_config.and_then(|p| if p.inner().is_empty() { None } else { Some(p) }) { - kdl_string.push_str(&indent( - &format!("plugin location=\"{}\" {{\n", plugin), - INDENT, - )); + let mut plugin_node_children = KdlDocument::new(); for (config_key, config_value) in plugin_config.inner() { - kdl_string.push_str(&indent( - &format!("{} \"{}\"\n", config_key, config_value), - INDENT, - )); + let mut config_node = KdlNode::new(config_key.to_owned()); + config_node + .entries_mut() + .push(KdlEntry::new(config_value.to_owned())); + plugin_node_children.nodes_mut().push(config_node); } - kdl_string.push_str(&indent("}\n", INDENT)); - } else { - kdl_string.push_str(&indent( - &format!("plugin location=\"{}\"\n", plugin), - INDENT, - )); + plugin_node.set_children(plugin_node_children); } + pane_node_children.nodes_mut().push(plugin_node); } } -fn stringify_tiled_layout_attributes( +fn serialize_tiled_layout_attributes( layout: &TiledPaneLayout, ignore_size: bool, - kdl_string: &mut String, + kdl_node: &mut KdlNode, ) { if !ignore_size { match layout.split_size { - Some(SplitSize::Fixed(size)) => kdl_string.push_str(&format!(" size={size}")), - Some(SplitSize::Percent(size)) => kdl_string.push_str(&format!(" size=\"{size}%\"")), + Some(SplitSize::Fixed(size)) => kdl_node + .entries_mut() + .push(KdlEntry::new_prop("size", KdlValue::Base10(size as i64))), + Some(SplitSize::Percent(size)) => kdl_node + .entries_mut() + .push(KdlEntry::new_prop("size", format!("{size}%"))), None => (), }; } if layout.borderless { - kdl_string.push_str(&" borderless=true"); + kdl_node + .entries_mut() + .push(KdlEntry::new_prop("borderless", KdlValue::Bool(true))); } if layout.children_are_stacked { - kdl_string.push_str(&" stacked=true"); + kdl_node + .entries_mut() + .push(KdlEntry::new_prop("stacked", KdlValue::Bool(true))); } if layout.is_expanded_in_stack { - kdl_string.push_str(&" expanded=true"); + kdl_node + .entries_mut() + .push(KdlEntry::new_prop("expanded", KdlValue::Bool(true))); } if layout.children_split_direction != SplitDirection::default() { let direction = match layout.children_split_direction { SplitDirection::Horizontal => "horizontal", SplitDirection::Vertical => "vertical", }; - kdl_string.push_str(&format!(" split_direction=\"{direction}\"")); + kdl_node + .entries_mut() + .push(KdlEntry::new_prop("split_direction", direction)); } } -fn stringify_floating_layout_attributes(layout: &FloatingPaneLayout, kdl_string: &mut String) { +fn serialize_floating_layout_attributes( + layout: &FloatingPaneLayout, + pane_node_children: &mut KdlDocument, +) { match layout.height { Some(PercentOrFixed::Fixed(fixed_height)) => { - kdl_string.push_str(&indent(&format!("height {}\n", fixed_height), INDENT)); + let mut node = KdlNode::new("height"); + node.entries_mut() + .push(KdlEntry::new(KdlValue::Base10(fixed_height as i64))); + pane_node_children.nodes_mut().push(node); }, Some(PercentOrFixed::Percent(percent)) => { - kdl_string.push_str(&indent(&format!("height \"{}%\"\n", percent), INDENT)); + let mut node = KdlNode::new("height"); + node.entries_mut() + .push(KdlEntry::new(format!("{}%", percent))); + pane_node_children.nodes_mut().push(node); }, None => {}, } match layout.width { Some(PercentOrFixed::Fixed(fixed_width)) => { - kdl_string.push_str(&indent(&format!("width {}\n", fixed_width), INDENT)); + let mut node = KdlNode::new("width"); + node.entries_mut() + .push(KdlEntry::new(KdlValue::Base10(fixed_width as i64))); + pane_node_children.nodes_mut().push(node); }, Some(PercentOrFixed::Percent(percent)) => { - kdl_string.push_str(&indent(&format!("width \"{}%\"\n", percent), INDENT)); + let mut node = KdlNode::new("width"); + node.entries_mut() + .push(KdlEntry::new(format!("{}%", percent))); + pane_node_children.nodes_mut().push(node); }, None => {}, } match layout.x { Some(PercentOrFixed::Fixed(fixed_x)) => { - kdl_string.push_str(&indent(&format!("x {}\n", fixed_x), INDENT)); + let mut node = KdlNode::new("x"); + node.entries_mut() + .push(KdlEntry::new(KdlValue::Base10(fixed_x as i64))); + pane_node_children.nodes_mut().push(node); }, Some(PercentOrFixed::Percent(percent)) => { - kdl_string.push_str(&indent(&format!("x \"{}%\"\n", percent), INDENT)); + let mut node = KdlNode::new("x"); + node.entries_mut() + .push(KdlEntry::new(format!("{}%", percent))); + pane_node_children.nodes_mut().push(node); }, None => {}, } match layout.y { Some(PercentOrFixed::Fixed(fixed_y)) => { - kdl_string.push_str(&indent(&format!("y {}\n", fixed_y), INDENT)); + let mut node = KdlNode::new("y"); + node.entries_mut() + .push(KdlEntry::new(KdlValue::Base10(fixed_y as i64))); + pane_node_children.nodes_mut().push(node); }, Some(PercentOrFixed::Percent(percent)) => { - kdl_string.push_str(&indent(&format!("y \"{}%\"\n", percent), INDENT)); + let mut node = KdlNode::new("y"); + node.entries_mut() + .push(KdlEntry::new(format!("{}%", percent))); + pane_node_children.nodes_mut().push(node); }, None => {}, } } -fn stringify_start_suspended(command: &Option, kdl_string: &mut String) { +fn serialize_start_suspended(command: &Option, pane_node_children: &mut KdlDocument) { if command.is_some() { - kdl_string.push_str(&indent(&"start_suspended true\n", INDENT)); + let mut start_suspended_node = KdlNode::new("start_suspended"); + start_suspended_node + .entries_mut() + .push(KdlEntry::new(KdlValue::Bool(true))); + pane_node_children.nodes_mut().push(start_suspended_node); } } -fn stringify_global_cwd(global_cwd: &Option, kdl_string: &mut String) { - if let Some(global_cwd) = global_cwd { - kdl_string.push_str(&indent( - &format!("cwd \"{}\"\n", global_cwd.display()), - INDENT, - )); - } +fn serialize_global_cwd(global_cwd: &Option) -> Option { + global_cwd.as_ref().map(|cwd| { + let mut node = KdlNode::new("cwd"); + node.push(cwd.display().to_string()); + node + }) } -fn stringify_new_tab_template( +fn serialize_new_tab_template( new_tab_template: Option<(TiledPaneLayout, Vec)>, pane_contents: &mut BTreeMap, - kdl_string: &mut String, + layout_children_node: &mut KdlDocument, ) { if let Some((tiled_panes, floating_panes)) = new_tab_template { let tiled_panes = if &tiled_panes.children_split_direction != &SplitDirection::default() { @@ -428,33 +479,35 @@ fn stringify_new_tab_template( } else { tiled_panes.children }; - kdl_string.push_str(&indent( - &kdl_string_from_tab( - &tiled_panes, - &floating_panes, - vec![], - Some(String::from("new_tab_template")), - pane_contents, - ), - INDENT, - )); + let mut new_tab_template_node = KdlNode::new("new_tab_template"); + let mut new_tab_template_children = KdlDocument::new(); + + serialize_tiled_and_floating_panes( + &tiled_panes, + floating_panes, + pane_contents, + &mut new_tab_template_children, + ); + new_tab_template_node.set_children(new_tab_template_children); + layout_children_node.nodes_mut().push(new_tab_template_node); } } -fn stringify_swap_tiled_layouts( +fn serialize_swap_tiled_layouts( swap_tiled_layouts: Vec, pane_contents: &mut BTreeMap, - kdl_string: &mut String, + layout_node_children: &mut KdlDocument, ) { for swap_tiled_layout in swap_tiled_layouts { + let mut swap_tiled_layout_node = KdlNode::new("swap_tiled_layout"); + let mut swap_tiled_layout_node_children = KdlDocument::new(); let swap_tiled_layout_name = swap_tiled_layout.1; - match &swap_tiled_layout_name { - Some(name) => kdl_string.push_str(&indent( - &format!("swap_tiled_layout name=\"{}\" {{\n", name), - INDENT, - )), - None => kdl_string.push_str(&indent("swap_tiled_layout {\n", INDENT)), - }; + if let Some(name) = swap_tiled_layout_name { + swap_tiled_layout_node + .entries_mut() + .push(KdlEntry::new_prop("name", name.to_owned())); + } + for (layout_constraint, tiled_panes_layout) in swap_tiled_layout.0 { let tiled_panes_layout = if &tiled_panes_layout.children_split_direction != &SplitDirection::default() { @@ -462,71 +515,99 @@ fn stringify_swap_tiled_layouts( } else { tiled_panes_layout.children }; - kdl_string.push_str(&indent( - &kdl_string_from_tab( - &tiled_panes_layout, - &vec![], - vec![layout_constraint.to_string()], - None, - pane_contents, - ), - DOUBLE_INDENT, - )); + let mut layout_step_node = KdlNode::new("tab"); + let mut layout_step_node_children = KdlDocument::new(); + if let Some(layout_constraint_entry) = serialize_layout_constraint(layout_constraint) { + layout_step_node.entries_mut().push(layout_constraint_entry); + } + + serialize_tiled_and_floating_panes( + &tiled_panes_layout, + vec![], + pane_contents, + &mut layout_step_node_children, + ); + layout_step_node.set_children(layout_step_node_children); + swap_tiled_layout_node_children + .nodes_mut() + .push(layout_step_node); } - kdl_string.push_str(&indent("}", INDENT)); + swap_tiled_layout_node.set_children(swap_tiled_layout_node_children); + layout_node_children + .nodes_mut() + .push(swap_tiled_layout_node); } } -fn stringify_swap_floating_layouts( +fn serialize_layout_constraint(layout_constraint: LayoutConstraint) -> Option { + match layout_constraint { + LayoutConstraint::MaxPanes(max_panes) => Some(KdlEntry::new_prop( + "max_panes", + KdlValue::Base10(max_panes as i64), + )), + LayoutConstraint::MinPanes(min_panes) => Some(KdlEntry::new_prop( + "min_panes", + KdlValue::Base10(min_panes as i64), + )), + LayoutConstraint::ExactPanes(exact_panes) => Some(KdlEntry::new_prop( + "exact_panes", + KdlValue::Base10(exact_panes as i64), + )), + LayoutConstraint::NoConstraint => None, + } +} + +fn serialize_swap_floating_layouts( swap_floating_layouts: Vec, pane_contents: &mut BTreeMap, - kdl_string: &mut String, + layout_children_node: &mut KdlDocument, ) { for swap_floating_layout in swap_floating_layouts { + let mut swap_floating_layout_node = KdlNode::new("swap_floating_layout"); + let mut swap_floating_layout_node_children = KdlDocument::new(); let swap_floating_layout_name = swap_floating_layout.1; - match &swap_floating_layout_name { - Some(name) => kdl_string.push_str(&indent( - &format!("swap_floating_layout name=\"{}\" {{\n", name), - INDENT, - )), - None => kdl_string.push_str(&indent("swap_floating_layout {\n", INDENT)), - }; - for (layout_constraint, floating_panes_layout) in swap_floating_layout.0 { - let has_floating_panes = !floating_panes_layout.is_empty(); - if has_floating_panes { - kdl_string.push_str(&indent( - &format!("floating_panes {} {{\n", layout_constraint), - DOUBLE_INDENT, - )); - } else { - kdl_string.push_str(&indent( - &format!("floating_panes {}\n", layout_constraint), - DOUBLE_INDENT, - )); - } - for floating_pane_layout in floating_panes_layout { - let sub_kdl_string = - kdl_string_from_floating_pane(&floating_pane_layout, pane_contents); - kdl_string.push_str(&indent(&sub_kdl_string, TRIPLE_INDENT)); - } - if has_floating_panes { - kdl_string.push_str(&indent("}\n", DOUBLE_INDENT)); - } + if let Some(name) = swap_floating_layout_name { + swap_floating_layout_node + .entries_mut() + .push(KdlEntry::new_prop("name", name.to_owned())); } - kdl_string.push_str(&indent("}", INDENT)); + + for (layout_constraint, floating_panes_layout) in swap_floating_layout.0 { + let mut layout_step_node = KdlNode::new("floating_panes"); + let mut layout_step_node_children = KdlDocument::new(); + if let Some(layout_constraint_entry) = serialize_layout_constraint(layout_constraint) { + layout_step_node.entries_mut().push(layout_constraint_entry); + } + + for floating_pane_layout in floating_panes_layout { + let floating_pane_node = + serialize_floating_pane(&floating_pane_layout, pane_contents); + layout_step_node_children + .nodes_mut() + .push(floating_pane_node); + } + layout_step_node.set_children(layout_step_node_children); + swap_floating_layout_node_children + .nodes_mut() + .push(layout_step_node); + } + swap_floating_layout_node.set_children(swap_floating_layout_node_children); + layout_children_node + .nodes_mut() + .push(swap_floating_layout_node); } } -fn stringify_multiple_tabs( +fn serialize_multiple_tabs( tabs: Vec<(String, TabLayoutManifest)>, pane_contents: &mut BTreeMap, - kdl_string: &mut String, -) -> Result<(), &'static str> { +) -> Result, &'static str> { + let mut serialized_tabs: Vec = vec![]; for (tab_name, tab_layout_manifest) in tabs { let tiled_panes = tab_layout_manifest.tiled_panes; let floating_panes = tab_layout_manifest.floating_panes; let hide_floating_panes = tab_layout_manifest.hide_floating_panes; - let stringified = stringify_tab( + let serialized = serialize_tab( tab_name.clone(), tab_layout_manifest.is_focused, hide_floating_panes, @@ -534,28 +615,25 @@ fn stringify_multiple_tabs( &floating_panes, pane_contents, ); - match stringified { - Some(stringified) => { - kdl_string.push_str(&indent(&stringified, INDENT)); - }, - None => { - return Err("Failed to stringify tab"); - }, + if let Some(serialized) = serialized { + serialized_tabs.push(serialized); } } - Ok(()) + Ok(serialized_tabs) } -fn kdl_string_from_floating_pane( +fn serialize_floating_pane( layout: &FloatingPaneLayout, pane_contents: &mut BTreeMap, -) -> String { +) -> KdlNode { + let mut floating_pane_node = KdlNode::new("pane"); + let mut floating_pane_node_children = KdlDocument::new(); let (command, args) = extract_command_and_args(&layout.run); let (plugin, plugin_config) = extract_plugin_and_config(&layout.run); let (edit, _line_number) = extract_edit_and_line_number(&layout.run); let cwd = layout.run.as_ref().and_then(|r| r.get_cwd()); let has_children = false; - let mut kdl_string = stringify_pane_title_and_attributes( + serialize_pane_title_and_attributes( &command, &edit, &layout.name, @@ -564,14 +642,14 @@ fn kdl_string_from_floating_pane( &layout.pane_initial_contents, pane_contents, has_children, + &mut floating_pane_node, ); - kdl_string.push_str(" {\n"); - stringify_start_suspended(&command, &mut kdl_string); - stringify_floating_layout_attributes(&layout, &mut kdl_string); - stringify_args(args, &mut kdl_string); - stringify_plugin(plugin, plugin_config, &mut kdl_string); - kdl_string.push_str("}\n"); - kdl_string + serialize_start_suspended(&command, &mut floating_pane_node_children); + serialize_floating_layout_attributes(&layout, &mut floating_pane_node_children); + serialize_args(args, &mut floating_pane_node_children); + serialize_plugin(plugin, plugin_config, &mut floating_pane_node_children); + floating_pane_node.set_children(floating_pane_node_children); + floating_pane_node } fn tiled_pane_layout_from_manifest( @@ -762,7 +840,6 @@ fn get_splits(geoms: &Vec) -> Option<(SplitDirection, Vec>)>, geoms: &Vec, (_, x_max): &(usize, usize), (y_min, y_max): &(usize, usize), @@ -821,7 +898,6 @@ fn get_row_splits( /// Get the constraint of the domain considered, base on the rows or columns, /// depending on the split direction provided. fn get_domain_constraint( - // geoms: &Vec<(PaneGeom, Option>)>, geoms: &Vec, split_direction: &SplitDirection, (v_min, v_max): (usize, usize), @@ -832,7 +908,6 @@ fn get_domain_constraint( } } -// fn get_domain_col_constraint(geoms: &Vec<(PaneGeom, Option>)>, (x_min, x_max): (usize, usize)) -> Constraint { fn get_domain_col_constraint( geoms: &Vec, (x_min, x_max): (usize, usize), @@ -861,7 +936,6 @@ fn get_domain_col_constraint( } } -// fn get_domain_row_constraint(geoms: &Vec<(PaneGeom, Option>)>, (y_min, y_max): (usize, usize)) -> Constraint { fn get_domain_row_constraint( geoms: &Vec, (y_min, y_max): (usize, usize), @@ -923,6 +997,7 @@ mod tests { use super::*; use crate::pane_size::Dimension; use expect_test::expect; + use insta::assert_snapshot; use serde_json::Value; use std::collections::HashMap; const PANEGEOMS_JSON: &[&[&str]] = &[ @@ -981,15 +1056,16 @@ mod tests { tabs: vec![("Tab #1".to_owned(), tab_layout_manifest)], ..Default::default() }; - // let kdl = kdl_string_from_panegeoms(&geoms); let kdl = serialize_session_layout(global_layout_manifest).unwrap(); - expect![[r#"layout { - tab name="Tab #1" { - pane size=1 - pane - pane size=2 - } -}"#]] + expect![[r#" + layout { + tab name="Tab #1" { + pane size=1 + pane + pane size=2 + } + } + "#]] .assert_eq(&kdl.0); let geoms = PANEGEOMS_JSON[1] @@ -1009,15 +1085,17 @@ mod tests { ..Default::default() }; let kdl = serialize_session_layout(global_layout_manifest).unwrap(); - expect![[r#"layout { - tab name="Tab #1" { - pane - pane size=20 split_direction="vertical" { - pane size=50 - pane - } - } -}"#]] + expect![[r#" + layout { + tab name="Tab #1" { + pane + pane size=20 split_direction="vertical" { + pane size=50 + pane + } + } + } + "#]] .assert_eq(&kdl.0); let geoms = PANEGEOMS_JSON[2] @@ -1037,23 +1115,25 @@ mod tests { ..Default::default() }; let kdl = serialize_session_layout(global_layout_manifest).unwrap(); - expect![[r#"layout { - tab name="Tab #1" { - pane size=10 split_direction="vertical" { - pane size="50%" - pane size="50%" - } - pane split_direction="vertical" { - pane size=40 - pane - pane size=40 - } - pane size=10 split_direction="vertical" { - pane size="50%" - pane size="50%" - } - } -}"#]] + expect![[r#" + layout { + tab name="Tab #1" { + pane size=10 split_direction="vertical" { + pane size="50%" + pane size="50%" + } + pane split_direction="vertical" { + pane size=40 + pane + pane size=40 + } + pane size=10 split_direction="vertical" { + pane size="50%" + pane size="50%" + } + } + } + "#]] .assert_eq(&kdl.0); let geoms = PANEGEOMS_JSON[3] @@ -1073,24 +1153,26 @@ mod tests { ..Default::default() }; let kdl = serialize_session_layout(global_layout_manifest).unwrap(); - expect![[r#"layout { - tab name="Tab #1" { - pane split_direction="vertical" { - pane size="70%" { - pane split_direction="vertical" { - pane size="50%" { + expect![[r#" + layout { + tab name="Tab #1" { + pane split_direction="vertical" { + pane size="70%" { + pane split_direction="vertical" { + pane size="50%" { + pane size="30%" + pane size="30%" + pane size="40%" + } + pane size="50%" + } + pane size=10 + } pane size="30%" - pane size="30%" - pane size="40%" } - pane size="50%" } - pane size=10 } - pane size="30%" - } - } -}"#]] + "#]] .assert_eq(&kdl.0); let geoms = PANEGEOMS_JSON[4] @@ -1110,20 +1192,544 @@ mod tests { ..Default::default() }; let kdl = serialize_session_layout(global_layout_manifest).unwrap(); - expect![[r#"layout { - tab name="Tab #1" { - pane size=5 - pane split_direction="vertical" { - pane size=20 - pane size="50%" - pane size="50%" - pane size=20 - } - pane size=5 - } -}"#]] + expect![[r#" + layout { + tab name="Tab #1" { + pane size=5 + pane split_direction="vertical" { + pane size=20 + pane size="50%" + pane size="50%" + pane size=20 + } + pane size=5 + } + } + "#]] .assert_eq(&kdl.0); } + + #[test] + fn global_cwd() { + let global_layout_manifest = GlobalLayoutManifest { + global_cwd: Some(PathBuf::from("/path/to/m\"y/global cwd")), + ..Default::default() + }; + let kdl = serialize_session_layout(global_layout_manifest).unwrap(); + assert_snapshot!(kdl.0); + } + + #[test] + fn can_serialize_tab_name() { + let global_layout_manifest = GlobalLayoutManifest { + tabs: vec![("my \"tab \\name".to_owned(), TabLayoutManifest::default())], + ..Default::default() + }; + let kdl = serialize_session_layout(global_layout_manifest).unwrap(); + assert_snapshot!(kdl.0); + } + #[test] + fn can_serialize_tab_focus() { + let tab_layout_manifest = TabLayoutManifest { + is_focused: true, + ..Default::default() + }; + let global_layout_manifest = GlobalLayoutManifest { + tabs: vec![("Tab #1".to_owned(), tab_layout_manifest)], + ..Default::default() + }; + let kdl = serialize_session_layout(global_layout_manifest).unwrap(); + assert_snapshot!(kdl.0); + } + #[test] + fn can_serialize_tab_hide_floating_panes() { + let tab_layout_manifest = TabLayoutManifest { + hide_floating_panes: true, + ..Default::default() + }; + let global_layout_manifest = GlobalLayoutManifest { + tabs: vec![("Tab #1".to_owned(), tab_layout_manifest)], + ..Default::default() + }; + let kdl = serialize_session_layout(global_layout_manifest).unwrap(); + assert_snapshot!(kdl.0); + } + #[test] + fn can_serialize_tab_with_tiled_panes() { + let mut plugin_configuration = BTreeMap::new(); + plugin_configuration.insert("key 1\"\\".to_owned(), "val 1\"\\".to_owned()); + plugin_configuration.insert("key 2\"\\".to_owned(), "val 2\"\\".to_owned()); + let tab_layout_manifest = TabLayoutManifest { + tiled_panes: vec![ + PaneLayoutManifest { + geom: PaneGeom { + x: 0, + y: 0, + rows: Dimension::fixed(10), + cols: Dimension::fixed(10), + is_stacked: false, + }, + ..Default::default() + }, + PaneLayoutManifest { + run: Some(Run::Cwd(PathBuf::from("/tmp/\"my/cool cwd"))), + geom: PaneGeom { + x: 0, + y: 10, + rows: Dimension::fixed(10), + cols: Dimension::fixed(10), + is_stacked: false, + }, + ..Default::default() + }, + PaneLayoutManifest { + run: Some(Run::EditFile( + PathBuf::from("/tmp/\"my/cool cwd/my-file"), + None, + None, + )), + geom: PaneGeom { + x: 0, + y: 20, + rows: Dimension::fixed(10), + cols: Dimension::fixed(10), + is_stacked: false, + }, + ..Default::default() + }, + PaneLayoutManifest { + run: Some(Run::Command(RunCommand { + command: PathBuf::from("/tmp/\"my/cool cwd/command.sh"), + ..Default::default() + })), + geom: PaneGeom { + x: 0, + y: 30, + rows: Dimension::fixed(10), + cols: Dimension::fixed(10), + is_stacked: false, + }, + ..Default::default() + }, + PaneLayoutManifest { + run: Some(Run::Command(RunCommand { + command: PathBuf::from("/tmp/\"my/cool cwd/command.sh"), + args: vec![ + "--arg1".to_owned(), + "arg\"2".to_owned(), + "arg > \\3".to_owned(), + ], + ..Default::default() + })), + geom: PaneGeom { + x: 0, + y: 40, + rows: Dimension::fixed(10), + cols: Dimension::fixed(10), + is_stacked: false, + }, + ..Default::default() + }, + PaneLayoutManifest { + run: Some(Run::Plugin(RunPluginOrAlias::RunPlugin( + RunPlugin::from_url("file:/tmp/\"my/cool cwd/plugin.wasm").unwrap(), + ))), + geom: PaneGeom { + x: 0, + y: 50, + rows: Dimension::fixed(10), + cols: Dimension::fixed(10), + is_stacked: false, + }, + ..Default::default() + }, + PaneLayoutManifest { + run: Some(Run::Plugin(RunPluginOrAlias::RunPlugin( + RunPlugin::from_url("file:/tmp/\"my/cool cwd/plugin.wasm") + .unwrap() + .with_configuration(plugin_configuration), + ))), + geom: PaneGeom { + x: 0, + y: 60, + rows: Dimension::fixed(10), + cols: Dimension::fixed(10), + is_stacked: false, + }, + ..Default::default() + }, + PaneLayoutManifest { + is_borderless: true, + geom: PaneGeom { + x: 0, + y: 70, + rows: Dimension::fixed(10), + cols: Dimension::fixed(10), + is_stacked: false, + }, + ..Default::default() + }, + PaneLayoutManifest { + title: Some("my cool \\ \"pane_title\"".to_owned()), + is_focused: true, + pane_contents: Some("can has pane contents".to_owned()), + geom: PaneGeom { + x: 0, + y: 80, + rows: Dimension::fixed(10), + cols: Dimension::fixed(10), + is_stacked: false, + }, + ..Default::default() + }, + ], + ..Default::default() + }; + let global_layout_manifest = GlobalLayoutManifest { + tabs: vec![("Tab with \"tiled panes\"".to_owned(), tab_layout_manifest)], + ..Default::default() + }; + let kdl = serialize_session_layout(global_layout_manifest).unwrap(); + assert_snapshot!(kdl.0); + } + #[test] + fn can_serialize_tab_with_floating_panes() { + let mut plugin_configuration = BTreeMap::new(); + plugin_configuration.insert("key 1\"\\".to_owned(), "val 1\"\\".to_owned()); + plugin_configuration.insert("key 2\"\\".to_owned(), "val 2\"\\".to_owned()); + let tab_layout_manifest = TabLayoutManifest { + floating_panes: vec![ + PaneLayoutManifest { + geom: PaneGeom { + x: 0, + y: 0, + rows: Dimension::fixed(10), + cols: Dimension::fixed(10), + is_stacked: false, + }, + ..Default::default() + }, + PaneLayoutManifest { + run: Some(Run::Cwd(PathBuf::from("/tmp/\"my/cool cwd"))), + geom: PaneGeom { + x: 0, + y: 10, + rows: Dimension::fixed(10), + cols: Dimension::fixed(10), + is_stacked: false, + }, + ..Default::default() + }, + PaneLayoutManifest { + run: Some(Run::EditFile( + PathBuf::from("/tmp/\"my/cool cwd/my-file"), + None, + None, + )), + geom: PaneGeom { + x: 0, + y: 20, + rows: Dimension::fixed(10), + cols: Dimension::fixed(10), + is_stacked: false, + }, + ..Default::default() + }, + PaneLayoutManifest { + run: Some(Run::Command(RunCommand { + command: PathBuf::from("/tmp/\"my/cool cwd/command.sh"), + ..Default::default() + })), + geom: PaneGeom { + x: 0, + y: 30, + rows: Dimension::fixed(10), + cols: Dimension::fixed(10), + is_stacked: false, + }, + ..Default::default() + }, + PaneLayoutManifest { + run: Some(Run::Command(RunCommand { + command: PathBuf::from("/tmp/\"my/cool cwd/command.sh"), + args: vec![ + "--arg1".to_owned(), + "arg\"2".to_owned(), + "arg > \\3".to_owned(), + ], + ..Default::default() + })), + geom: PaneGeom { + x: 0, + y: 40, + rows: Dimension::fixed(10), + cols: Dimension::fixed(10), + is_stacked: false, + }, + ..Default::default() + }, + PaneLayoutManifest { + run: Some(Run::Plugin(RunPluginOrAlias::RunPlugin( + RunPlugin::from_url("file:/tmp/\"my/cool cwd/plugin.wasm").unwrap(), + ))), + geom: PaneGeom { + x: 0, + y: 50, + rows: Dimension::fixed(10), + cols: Dimension::fixed(10), + is_stacked: false, + }, + ..Default::default() + }, + PaneLayoutManifest { + run: Some(Run::Plugin(RunPluginOrAlias::RunPlugin( + RunPlugin::from_url("file:/tmp/\"my/cool cwd/plugin.wasm") + .unwrap() + .with_configuration(plugin_configuration), + ))), + geom: PaneGeom { + x: 0, + y: 60, + rows: Dimension::fixed(10), + cols: Dimension::fixed(10), + is_stacked: false, + }, + ..Default::default() + }, + PaneLayoutManifest { + // note that in this case, `is_borderless` should be ignored because this is a + // floating pane + is_borderless: true, + geom: PaneGeom { + x: 0, + y: 70, + rows: Dimension::fixed(10), + cols: Dimension::fixed(10), + is_stacked: false, + }, + ..Default::default() + }, + PaneLayoutManifest { + title: Some("my cool \\ \"pane_title\"".to_owned()), + is_focused: true, + pane_contents: Some("can has pane contents".to_owned()), + geom: PaneGeom { + x: 0, + y: 80, + rows: Dimension::fixed(10), + cols: Dimension::fixed(10), + is_stacked: false, + }, + ..Default::default() + }, + ], + ..Default::default() + }; + let global_layout_manifest = GlobalLayoutManifest { + tabs: vec![( + "Tab with \"floating panes\"".to_owned(), + tab_layout_manifest, + )], + ..Default::default() + }; + let kdl = serialize_session_layout(global_layout_manifest).unwrap(); + assert_snapshot!(kdl.0); + } + #[test] + fn can_serialize_tab_with_stacked_panes() { + let tab_layout_manifest = TabLayoutManifest { + tiled_panes: vec![ + PaneLayoutManifest { + geom: PaneGeom { + x: 0, + y: 0, + rows: Dimension::fixed(1), + cols: Dimension::fixed(10), + is_stacked: true, + }, + ..Default::default() + }, + PaneLayoutManifest { + geom: PaneGeom { + x: 0, + y: 1, + rows: Dimension::fixed(10), + cols: Dimension::fixed(10), + is_stacked: true, + }, + ..Default::default() + }, + PaneLayoutManifest { + geom: PaneGeom { + x: 0, + y: 11, + rows: Dimension::fixed(1), + cols: Dimension::fixed(10), + is_stacked: true, + }, + ..Default::default() + }, + ], + ..Default::default() + }; + let global_layout_manifest = GlobalLayoutManifest { + tabs: vec![("Tab with \"stacked panes\"".to_owned(), tab_layout_manifest)], + ..Default::default() + }; + let kdl = serialize_session_layout(global_layout_manifest).unwrap(); + assert_snapshot!(kdl.0); + } + #[test] + fn can_serialize_multiple_tabs() { + let tab_1_layout_manifest = TabLayoutManifest { + tiled_panes: vec![PaneLayoutManifest { + geom: PaneGeom { + x: 0, + y: 0, + rows: Dimension::percent(100.0), + cols: Dimension::percent(100.0), + is_stacked: false, + }, + ..Default::default() + }], + ..Default::default() + }; + let tab_2_layout_manifest = TabLayoutManifest { + tiled_panes: vec![ + PaneLayoutManifest { + geom: PaneGeom { + x: 0, + y: 0, + rows: Dimension::fixed(10), + cols: Dimension::fixed(10), + is_stacked: false, + }, + ..Default::default() + }, + PaneLayoutManifest { + geom: PaneGeom { + x: 10, + y: 0, + rows: Dimension::fixed(10), + cols: Dimension::fixed(10), + is_stacked: false, + }, + ..Default::default() + }, + ], + ..Default::default() + }; + let global_layout_manifest = GlobalLayoutManifest { + tabs: vec![ + ("First tab".to_owned(), tab_1_layout_manifest), + ("Second tab".to_owned(), tab_2_layout_manifest), + ], + ..Default::default() + }; + let kdl = serialize_session_layout(global_layout_manifest).unwrap(); + assert_snapshot!(kdl.0); + } + #[test] + fn can_serialize_new_tab_template() { + let tiled_panes_layout = TiledPaneLayout { + children: vec![TiledPaneLayout::default(), TiledPaneLayout::default()], + ..Default::default() + }; + + let floating_panes_layout = vec![ + FloatingPaneLayout::default(), + FloatingPaneLayout::default(), + FloatingPaneLayout::default(), + ]; + let mut default_layout = Layout::default(); + default_layout.template = Some((tiled_panes_layout, floating_panes_layout)); + let default_layout = Box::new(default_layout); + let global_layout_manifest = GlobalLayoutManifest { + default_layout, + ..Default::default() + }; + let kdl = serialize_session_layout(global_layout_manifest).unwrap(); + assert_snapshot!(kdl.0); + } + #[test] + fn can_serialize_swap_tiled_panes() { + let tiled_panes_layout = TiledPaneLayout { + children: vec![TiledPaneLayout::default(), TiledPaneLayout::default()], + ..Default::default() + }; + let mut default_layout = Layout::default(); + let mut swap_tiled_layout_1 = BTreeMap::new(); + let mut swap_tiled_layout_2 = BTreeMap::new(); + swap_tiled_layout_1.insert(LayoutConstraint::MaxPanes(1), tiled_panes_layout.clone()); + swap_tiled_layout_1.insert(LayoutConstraint::MinPanes(1), tiled_panes_layout.clone()); + swap_tiled_layout_1.insert(LayoutConstraint::ExactPanes(1), tiled_panes_layout.clone()); + swap_tiled_layout_1.insert(LayoutConstraint::NoConstraint, tiled_panes_layout.clone()); + swap_tiled_layout_2.insert(LayoutConstraint::MaxPanes(2), tiled_panes_layout.clone()); + swap_tiled_layout_2.insert(LayoutConstraint::MinPanes(2), tiled_panes_layout.clone()); + swap_tiled_layout_2.insert(LayoutConstraint::ExactPanes(2), tiled_panes_layout.clone()); + swap_tiled_layout_2.insert(LayoutConstraint::NoConstraint, tiled_panes_layout.clone()); + + let swap_tiled_layouts = vec![ + (swap_tiled_layout_1, None), + (swap_tiled_layout_2, Some("swap_tiled_layout_2".to_owned())), + ]; + default_layout.swap_tiled_layouts = swap_tiled_layouts; + let default_layout = Box::new(default_layout); + let global_layout_manifest = GlobalLayoutManifest { + default_layout, + ..Default::default() + }; + let kdl = serialize_session_layout(global_layout_manifest).unwrap(); + assert_snapshot!(kdl.0); + } + #[test] + fn can_serialize_swap_floating_panes() { + let floating_panes_layout = vec![ + FloatingPaneLayout::default(), + FloatingPaneLayout::default(), + FloatingPaneLayout::default(), + ]; + let mut default_layout = Layout::default(); + let mut swap_floating_layout_1 = BTreeMap::new(); + let mut swap_floating_layout_2 = BTreeMap::new(); + swap_floating_layout_1.insert(LayoutConstraint::MaxPanes(1), floating_panes_layout.clone()); + swap_floating_layout_1.insert(LayoutConstraint::MinPanes(1), floating_panes_layout.clone()); + swap_floating_layout_1.insert( + LayoutConstraint::ExactPanes(1), + floating_panes_layout.clone(), + ); + swap_floating_layout_1.insert( + LayoutConstraint::NoConstraint, + floating_panes_layout.clone(), + ); + swap_floating_layout_2.insert(LayoutConstraint::MaxPanes(2), floating_panes_layout.clone()); + swap_floating_layout_2.insert(LayoutConstraint::MinPanes(2), floating_panes_layout.clone()); + swap_floating_layout_2.insert( + LayoutConstraint::ExactPanes(2), + floating_panes_layout.clone(), + ); + swap_floating_layout_2.insert( + LayoutConstraint::NoConstraint, + floating_panes_layout.clone(), + ); + + let swap_floating_layouts = vec![ + (swap_floating_layout_1, None), + ( + swap_floating_layout_2, + Some("swap_floating_layout_2".to_owned()), + ), + ]; + default_layout.swap_floating_layouts = swap_floating_layouts; + let default_layout = Box::new(default_layout); + let global_layout_manifest = GlobalLayoutManifest { + default_layout, + ..Default::default() + }; + let kdl = serialize_session_layout(global_layout_manifest).unwrap(); + assert_snapshot!(kdl.0); + } + // utility functions fn parse_panegeom_from_json(data_str: &str) -> PaneGeom { // diff --git a/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_multiple_tabs.snap b/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_multiple_tabs.snap new file mode 100644 index 00000000..6b688cbc --- /dev/null +++ b/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_multiple_tabs.snap @@ -0,0 +1,16 @@ +--- +source: zellij-utils/src/session_serialization.rs +assertion_line: 2025 +expression: kdl.0 +--- +layout { + tab name="First tab" { + } + tab name="Second tab" { + pane split_direction="vertical" { + pane size=10 + pane size=10 + } + } +} + diff --git a/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_new_tab_template.snap b/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_new_tab_template.snap new file mode 100644 index 00000000..a408d48d --- /dev/null +++ b/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_new_tab_template.snap @@ -0,0 +1,20 @@ +--- +source: zellij-utils/src/session_serialization.rs +assertion_line: 2047 +expression: kdl.0 +--- +layout { + new_tab_template { + pane + pane + floating_panes { + pane { + } + pane { + } + pane { + } + } + } +} + diff --git a/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_swap_floating_panes.snap b/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_swap_floating_panes.snap new file mode 100644 index 00000000..2b1838a8 --- /dev/null +++ b/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_swap_floating_panes.snap @@ -0,0 +1,76 @@ +--- +source: zellij-utils/src/session_serialization.rs +assertion_line: 2111 +expression: kdl.0 +--- +layout { + swap_floating_layout { + floating_panes max_panes=1 { + pane { + } + pane { + } + pane { + } + } + floating_panes min_panes=1 { + pane { + } + pane { + } + pane { + } + } + floating_panes exact_panes=1 { + pane { + } + pane { + } + pane { + } + } + floating_panes { + pane { + } + pane { + } + pane { + } + } + } + swap_floating_layout name="swap_floating_layout_2" { + floating_panes max_panes=2 { + pane { + } + pane { + } + pane { + } + } + floating_panes min_panes=2 { + pane { + } + pane { + } + pane { + } + } + floating_panes exact_panes=2 { + pane { + } + pane { + } + pane { + } + } + floating_panes { + pane { + } + pane { + } + pane { + } + } + } +} + diff --git a/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_swap_tiled_panes.snap b/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_swap_tiled_panes.snap new file mode 100644 index 00000000..8cc4562d --- /dev/null +++ b/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_swap_tiled_panes.snap @@ -0,0 +1,44 @@ +--- +source: zellij-utils/src/session_serialization.rs +assertion_line: 2079 +expression: kdl.0 +--- +layout { + swap_tiled_layout { + tab max_panes=1 { + pane + pane + } + tab min_panes=1 { + pane + pane + } + tab exact_panes=1 { + pane + pane + } + tab { + pane + pane + } + } + swap_tiled_layout name="swap_tiled_layout_2" { + tab max_panes=2 { + pane + pane + } + tab min_panes=2 { + pane + pane + } + tab exact_panes=2 { + pane + pane + } + tab { + pane + pane + } + } +} + diff --git a/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_tab_focus.snap b/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_tab_focus.snap new file mode 100644 index 00000000..4577fcf0 --- /dev/null +++ b/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_tab_focus.snap @@ -0,0 +1,10 @@ +--- +source: zellij-utils/src/session_serialization.rs +assertion_line: 1654 +expression: kdl.0 +--- +layout { + tab name="Tab #1" focus=true { + } +} + diff --git a/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_tab_hide_floating_panes.snap b/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_tab_hide_floating_panes.snap new file mode 100644 index 00000000..59c7a1df --- /dev/null +++ b/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_tab_hide_floating_panes.snap @@ -0,0 +1,10 @@ +--- +source: zellij-utils/src/session_serialization.rs +assertion_line: 1667 +expression: kdl.0 +--- +layout { + tab name="Tab #1" hide_floating_panes=true { + } +} + diff --git a/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_tab_name.snap b/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_tab_name.snap new file mode 100644 index 00000000..2d8b8897 --- /dev/null +++ b/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_tab_name.snap @@ -0,0 +1,10 @@ +--- +source: zellij-utils/src/session_serialization.rs +assertion_line: 1641 +expression: kdl.0 +--- +layout { + tab name="my \"tab \\name" { + } +} + diff --git a/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_tab_with_floating_panes.snap b/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_tab_with_floating_panes.snap new file mode 100644 index 00000000..e800233e --- /dev/null +++ b/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_tab_with_floating_panes.snap @@ -0,0 +1,74 @@ +--- +source: zellij-utils/src/session_serialization.rs +assertion_line: 1929 +expression: kdl.0 +--- +layout { + tab name="Tab with \"floating panes\"" { + floating_panes { + pane { + height 10 + width 10 + x 0 + y 0 + } + pane cwd="/tmp/\"my/cool cwd" { + height 10 + width 10 + x 0 + y 10 + } + pane edit="/tmp/\"my/cool cwd/my-file" { + height 10 + width 10 + x 0 + y 20 + } + pane command="/tmp/\"my/cool cwd/command.sh" { + start_suspended true + height 10 + width 10 + x 0 + y 30 + } + pane command="/tmp/\"my/cool cwd/command.sh" { + start_suspended true + height 10 + width 10 + x 0 + y 40 + args "--arg1" "arg\"2" "arg > \\3" + } + pane { + height 10 + width 10 + x 0 + y 50 + plugin location="file:/tmp/\"my/cool cwd/plugin.wasm" + } + pane { + height 10 + width 10 + x 0 + y 60 + plugin location="file:/tmp/\"my/cool cwd/plugin.wasm" { + "key 1\"\\" "val 1\"\\" + "key 2\"\\" "val 2\"\\" + } + } + pane { + height 10 + width 10 + x 0 + y 70 + } + pane name="my cool \\ \"pane_title\"" focus=true contents_file="initial_contents_1" { + height 10 + width 10 + x 0 + y 80 + } + } + } +} + diff --git a/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_tab_with_stacked_panes.snap b/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_tab_with_stacked_panes.snap new file mode 100644 index 00000000..ea6dc842 --- /dev/null +++ b/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_tab_with_stacked_panes.snap @@ -0,0 +1,15 @@ +--- +source: zellij-utils/src/session_serialization.rs +assertion_line: 1973 +expression: kdl.0 +--- +layout { + tab name="Tab with \"stacked panes\"" { + pane stacked=true { + pane + pane expanded=true + pane + } + } +} + diff --git a/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_tab_with_tiled_panes.snap b/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_tab_with_tiled_panes.snap new file mode 100644 index 00000000..f9ba3a25 --- /dev/null +++ b/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__can_serialize_tab_with_tiled_panes.snap @@ -0,0 +1,31 @@ +--- +source: zellij-utils/src/session_serialization.rs +assertion_line: 1799 +expression: kdl.0 +--- +layout { + tab name="Tab with \"tiled panes\"" { + pane size=10 + pane cwd="/tmp/\"my/cool cwd" size=10 + pane edit="/tmp/\"my/cool cwd/my-file" size=10 + pane command="/tmp/\"my/cool cwd/command.sh" size=10 { + start_suspended true + } + pane command="/tmp/\"my/cool cwd/command.sh" size=10 { + args "--arg1" "arg\"2" "arg > \\3" + start_suspended true + } + pane size=10 { + plugin location="file:/tmp/\"my/cool cwd/plugin.wasm" + } + pane size=10 { + plugin location="file:/tmp/\"my/cool cwd/plugin.wasm" { + "key 1\"\\" "val 1\"\\" + "key 2\"\\" "val 2\"\\" + } + } + pane size=10 borderless=true + pane name="my cool \\ \"pane_title\"" focus=true contents_file="initial_contents_1" size=10 + } +} + diff --git a/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__global_cwd.snap b/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__global_cwd.snap new file mode 100644 index 00000000..88403e9d --- /dev/null +++ b/zellij-utils/src/snapshots/zellij_utils__session_serialization__tests__global_cwd.snap @@ -0,0 +1,9 @@ +--- +source: zellij-utils/src/session_serialization.rs +assertion_line: 1168 +expression: kdl.0 +--- +layout { + cwd "/path/to/m\"y/global cwd" +} +