1use kdl::{KdlDocument, KdlEntry, KdlNode, KdlValue};
2use std::collections::{BTreeMap, HashMap, HashSet};
3use std::path::PathBuf;
4
5use crate::{
6 input::layout::PluginUserConfiguration,
7 input::layout::{
8 FloatingPaneLayout, Layout, LayoutConstraint, PercentOrFixed, Run, RunPluginOrAlias,
9 SplitDirection, SplitSize, SwapFloatingLayout, SwapTiledLayout, TiledPaneLayout,
10 },
11 pane_size::{Constraint, PaneGeom},
12};
13
14#[derive(Default, Debug, Clone)]
15pub struct GlobalLayoutManifest {
16 pub global_cwd: Option<PathBuf>,
17 pub default_shell: Option<PathBuf>,
18 pub default_layout: Box<Layout>,
19 pub tabs: Vec<(String, TabLayoutManifest)>,
20}
21
22#[derive(Default, Debug, Clone)]
23pub struct TabLayoutManifest {
24 pub tiled_panes: Vec<PaneLayoutManifest>,
25 pub floating_panes: Vec<PaneLayoutManifest>,
26 pub is_focused: bool,
27 pub hide_floating_panes: bool,
28}
29
30#[derive(Default, Debug, Clone)]
31pub struct PaneLayoutManifest {
32 pub geom: PaneGeom,
33 pub run: Option<Run>,
34 pub cwd: Option<PathBuf>,
35 pub is_borderless: bool,
36 pub title: Option<String>,
37 pub is_focused: bool,
38 pub pane_contents: Option<String>,
39 pub default_fg: Option<String>,
40 pub default_bg: Option<String>,
41}
42
43pub fn serialize_session_layout(
44 global_layout_manifest: GlobalLayoutManifest,
45) -> Result<(String, BTreeMap<String, String>), &'static str> {
46 let mut document = KdlDocument::new();
48 let mut pane_contents = BTreeMap::new();
49 let mut layout_node = KdlNode::new("layout");
50 let mut layout_node_children = KdlDocument::new();
51 if let Some(global_cwd) = serialize_global_cwd(&global_layout_manifest.global_cwd) {
52 layout_node_children.nodes_mut().push(global_cwd);
53 }
54 match serialize_multiple_tabs(global_layout_manifest.tabs, &mut pane_contents) {
55 Ok(mut serialized_tabs) => {
56 layout_node_children
57 .nodes_mut()
58 .append(&mut serialized_tabs);
59 },
60 Err(e) => {
61 return Err(e);
62 },
63 }
64 serialize_new_tab_template(
65 global_layout_manifest.default_layout.template,
66 &mut pane_contents,
67 &mut layout_node_children,
68 );
69 serialize_swap_tiled_layouts(
70 global_layout_manifest.default_layout.swap_tiled_layouts,
71 &mut pane_contents,
72 &mut layout_node_children,
73 );
74 serialize_swap_floating_layouts(
75 global_layout_manifest.default_layout.swap_floating_layouts,
76 &mut pane_contents,
77 &mut layout_node_children,
78 );
79
80 layout_node.set_children(layout_node_children);
81 document.nodes_mut().push(layout_node);
82 Ok((document.to_string(), pane_contents))
83}
84
85fn serialize_tab(
86 tab_name: String,
87 is_focused: bool,
88 hide_floating_panes: bool,
89 tiled_panes: &Vec<PaneLayoutManifest>,
90 floating_panes: &Vec<PaneLayoutManifest>,
91 pane_contents: &mut BTreeMap<String, String>,
92) -> Option<KdlNode> {
93 let mut serialized_tab = KdlNode::new("tab");
94 let mut serialized_tab_children = KdlDocument::new();
95 match get_tiled_panes_layout_from_panegeoms(tiled_panes, None) {
96 Some(tiled_panes_layout) => {
97 let floating_panes_layout = get_floating_panes_layout_from_panegeoms(floating_panes);
98 let tiled_panes = tiled_panes_to_serialize(tiled_panes_layout);
99 serialized_tab
100 .entries_mut()
101 .push(KdlEntry::new_prop("name", tab_name));
102 if is_focused {
103 serialized_tab
104 .entries_mut()
105 .push(KdlEntry::new_prop("focus", KdlValue::Bool(true)));
106 }
107 if hide_floating_panes {
108 serialized_tab.entries_mut().push(KdlEntry::new_prop(
109 "hide_floating_panes",
110 KdlValue::Bool(true),
111 ));
112 }
113
114 serialize_tiled_and_floating_panes(
115 &tiled_panes,
116 floating_panes_layout,
117 pane_contents,
118 &mut serialized_tab_children,
119 );
120
121 serialized_tab.set_children(serialized_tab_children);
122 Some(serialized_tab)
123 },
124 None => {
125 return None;
126 },
127 }
128}
129
130fn tiled_panes_to_serialize(root: TiledPaneLayout) -> Vec<TiledPaneLayout> {
131 let root_is_leaf = root.children.is_empty() && root.external_children_index.is_none();
132 if root_is_leaf {
133 if root == TiledPaneLayout::default() {
134 vec![]
135 } else {
136 vec![root]
137 }
138 } else if &root.children_split_direction != &SplitDirection::default()
139 || root.children_are_stacked
140 {
141 vec![root]
142 } else {
143 root.children
144 }
145}
146
147fn serialize_tiled_and_floating_panes(
148 tiled_panes: &Vec<TiledPaneLayout>,
149 floating_panes_layout: Vec<FloatingPaneLayout>,
150 pane_contents: &mut BTreeMap<String, String>,
151 serialized_tab_children: &mut KdlDocument,
152) {
153 for tiled_pane_layout in tiled_panes {
154 let ignore_size = false;
155 let tiled_pane_node = serialize_tiled_pane(tiled_pane_layout, ignore_size, pane_contents);
156 serialized_tab_children.nodes_mut().push(tiled_pane_node);
157 }
158 if !floating_panes_layout.is_empty() {
159 let mut floating_panes_node = KdlNode::new("floating_panes");
160 let mut floating_panes_node_children = KdlDocument::new();
161 for floating_pane in floating_panes_layout {
162 let pane_node = serialize_floating_pane(&floating_pane, pane_contents);
163 floating_panes_node_children.nodes_mut().push(pane_node);
164 }
165 floating_panes_node.set_children(floating_panes_node_children);
166 serialized_tab_children
167 .nodes_mut()
168 .push(floating_panes_node);
169 }
170}
171
172fn serialize_tiled_pane(
173 layout: &TiledPaneLayout,
174 ignore_size: bool,
175 pane_contents: &mut BTreeMap<String, String>,
176) -> KdlNode {
177 let (command, args) = extract_command_and_args(&layout.run);
178 let (plugin, plugin_config) = extract_plugin_and_config(&layout.run);
179 let (edit, _line_number) = extract_edit_and_line_number(&layout.run);
180 let cwd = layout.run.as_ref().and_then(|r| r.get_cwd());
181 let has_children = layout.external_children_index.is_some() || !layout.children.is_empty();
182
183 let mut tiled_pane_node = KdlNode::new("pane");
184 serialize_pane_title_and_attributes(
185 &command,
186 &edit,
187 &layout.name,
188 cwd,
189 layout.focus,
190 &layout.pane_initial_contents,
191 pane_contents,
192 has_children,
193 &mut tiled_pane_node,
194 );
195
196 serialize_tiled_layout_attributes(&layout, ignore_size, &mut tiled_pane_node);
197 if let Some(ref fg) = layout.default_fg {
198 tiled_pane_node
199 .entries_mut()
200 .push(KdlEntry::new_prop("default_fg", fg.to_owned()));
201 }
202 if let Some(ref bg) = layout.default_bg {
203 tiled_pane_node
204 .entries_mut()
205 .push(KdlEntry::new_prop("default_bg", bg.to_owned()));
206 }
207 let has_child_attributes = !layout.children.is_empty()
208 || layout.external_children_index.is_some()
209 || !args.is_empty()
210 || plugin.is_some()
211 || command.is_some();
212 if has_child_attributes {
213 let mut tiled_pane_node_children = KdlDocument::new();
214 serialize_args(args, &mut tiled_pane_node_children);
215 serialize_start_suspended(&command, &mut tiled_pane_node_children);
216 serialize_plugin(plugin, plugin_config, &mut tiled_pane_node_children);
217 if layout.children.is_empty() && layout.external_children_index.is_some() {
218 tiled_pane_node_children
219 .nodes_mut()
220 .push(KdlNode::new("children"));
221 }
222 for (i, pane) in layout.children.iter().enumerate() {
223 if Some(i) == layout.external_children_index {
224 tiled_pane_node_children
225 .nodes_mut()
226 .push(KdlNode::new("children"));
227 } else {
228 let ignore_size = layout.children_are_stacked;
229 let child_pane_node = serialize_tiled_pane(&pane, ignore_size, pane_contents);
230 tiled_pane_node_children.nodes_mut().push(child_pane_node);
231 }
232 }
233 tiled_pane_node.set_children(tiled_pane_node_children);
234 }
235 tiled_pane_node
236}
237
238pub fn extract_command_and_args(layout_run: &Option<Run>) -> (Option<String>, Vec<String>) {
239 match layout_run {
240 Some(Run::Command(run_command)) => (
241 Some(run_command.command.display().to_string()),
242 run_command.args.clone(),
243 ),
244 _ => (None, vec![]),
245 }
246}
247pub fn extract_plugin_and_config(
248 layout_run: &Option<Run>,
249) -> (Option<String>, Option<PluginUserConfiguration>) {
250 match &layout_run {
251 Some(Run::Plugin(run_plugin_or_alias)) => match run_plugin_or_alias {
252 RunPluginOrAlias::RunPlugin(run_plugin) => (
253 Some(run_plugin.location.display()),
254 Some(run_plugin.configuration.clone()),
255 ),
256 RunPluginOrAlias::Alias(plugin_alias) => {
257 let name = plugin_alias
261 .run_plugin
262 .as_ref()
263 .map(|run_plugin| run_plugin.location.display().to_string())
264 .unwrap_or_else(|| plugin_alias.name.clone());
265 let configuration = plugin_alias
266 .run_plugin
267 .as_ref()
268 .map(|run_plugin| run_plugin.configuration.clone());
269 (Some(name), configuration)
270 },
271 },
272 _ => (None, None),
273 }
274}
275pub fn extract_edit_and_line_number(layout_run: &Option<Run>) -> (Option<String>, Option<usize>) {
276 match &layout_run {
277 Some(Run::EditFile(path, line_number, _cwd)) => {
279 (Some(path.display().to_string()), line_number.clone())
280 },
281 _ => (None, None),
282 }
283}
284
285fn serialize_pane_title_and_attributes(
286 command: &Option<String>,
287 edit: &Option<String>,
288 name: &Option<String>,
289 cwd: Option<PathBuf>,
290 focus: Option<bool>,
291 initial_pane_contents: &Option<String>,
292 pane_contents: &mut BTreeMap<String, String>,
293 has_children: bool,
294 kdl_node: &mut KdlNode,
295) {
296 match (&command, &edit) {
297 (Some(command), _) => kdl_node
298 .entries_mut()
299 .push(KdlEntry::new_prop("command", command.to_owned())),
300 (None, Some(edit)) => kdl_node
301 .entries_mut()
302 .push(KdlEntry::new_prop("edit", edit.to_owned())),
303 _ => {},
304 };
305 if let Some(name) = name {
306 kdl_node
307 .entries_mut()
308 .push(KdlEntry::new_prop("name", name.to_owned()));
309 }
310 if let Some(cwd) = cwd {
311 let path = cwd.display().to_string();
312 if !path.is_empty() && !has_children {
313 kdl_node
314 .entries_mut()
315 .push(KdlEntry::new_prop("cwd", path.to_owned()));
316 }
317 }
318 if focus.unwrap_or(false) {
319 kdl_node
320 .entries_mut()
321 .push(KdlEntry::new_prop("focus", KdlValue::Bool(true)));
322 }
323 if let Some(initial_pane_contents) = initial_pane_contents.as_ref() {
324 if command.is_none() && edit.is_none() {
325 let file_name = format!("initial_contents_{}", pane_contents.keys().len() + 1);
326 kdl_node
327 .entries_mut()
328 .push(KdlEntry::new_prop("contents_file", file_name.clone()));
329
330 pane_contents.insert(file_name, initial_pane_contents.clone());
331 }
332 }
333}
334
335fn serialize_args(args: Vec<String>, pane_node_children: &mut KdlDocument) {
336 if !args.is_empty() {
337 let mut args_node = KdlNode::new("args");
338 for arg in &args {
339 args_node.entries_mut().push(KdlEntry::new(arg.to_owned()));
340 }
341 pane_node_children.nodes_mut().push(args_node);
342 }
343}
344
345fn serialize_plugin(
346 plugin: Option<String>,
347 plugin_config: Option<PluginUserConfiguration>,
348 pane_node_children: &mut KdlDocument,
349) {
350 if let Some(plugin) = plugin {
351 let mut plugin_node = KdlNode::new("plugin");
352 plugin_node
353 .entries_mut()
354 .push(KdlEntry::new_prop("location", plugin.to_owned()));
355 if let Some(plugin_config) =
356 plugin_config.and_then(|p| if p.inner().is_empty() { None } else { Some(p) })
357 {
358 let mut plugin_node_children = KdlDocument::new();
359 for (config_key, config_value) in plugin_config.inner() {
360 let mut config_node = KdlNode::new(config_key.to_owned());
361 config_node
362 .entries_mut()
363 .push(KdlEntry::new(config_value.to_owned()));
364 plugin_node_children.nodes_mut().push(config_node);
365 }
366 plugin_node.set_children(plugin_node_children);
367 }
368 pane_node_children.nodes_mut().push(plugin_node);
369 }
370}
371
372fn serialize_tiled_layout_attributes(
373 layout: &TiledPaneLayout,
374 ignore_size: bool,
375 kdl_node: &mut KdlNode,
376) {
377 if !ignore_size {
378 match layout.split_size {
379 Some(SplitSize::Fixed(size)) => kdl_node
380 .entries_mut()
381 .push(KdlEntry::new_prop("size", KdlValue::Base10(size as i64))),
382 Some(SplitSize::Percent(size)) => kdl_node
383 .entries_mut()
384 .push(KdlEntry::new_prop("size", format!("{size}%"))),
385 None => (),
386 };
387 }
388 if layout.borderless.unwrap_or(false) {
389 kdl_node
390 .entries_mut()
391 .push(KdlEntry::new_prop("borderless", KdlValue::Bool(true)));
392 }
393 if layout.children_are_stacked {
394 kdl_node
395 .entries_mut()
396 .push(KdlEntry::new_prop("stacked", KdlValue::Bool(true)));
397 }
398 if layout.is_expanded_in_stack {
399 kdl_node
400 .entries_mut()
401 .push(KdlEntry::new_prop("expanded", KdlValue::Bool(true)));
402 }
403 if layout.children_split_direction != SplitDirection::default() {
404 let direction = match layout.children_split_direction {
405 SplitDirection::Horizontal => "horizontal",
406 SplitDirection::Vertical => "vertical",
407 };
408 kdl_node
409 .entries_mut()
410 .push(KdlEntry::new_prop("split_direction", direction));
411 }
412}
413
414fn serialize_floating_layout_attributes(
415 layout: &FloatingPaneLayout,
416 pane_node_children: &mut KdlDocument,
417) {
418 match layout.height {
419 Some(PercentOrFixed::Fixed(fixed_height)) => {
420 let mut node = KdlNode::new("height");
421 node.entries_mut()
422 .push(KdlEntry::new(KdlValue::Base10(fixed_height as i64)));
423 pane_node_children.nodes_mut().push(node);
424 },
425 Some(PercentOrFixed::Percent(percent)) => {
426 let mut node = KdlNode::new("height");
427 node.entries_mut()
428 .push(KdlEntry::new(format!("{}%", percent)));
429 pane_node_children.nodes_mut().push(node);
430 },
431 None => {},
432 }
433 match layout.width {
434 Some(PercentOrFixed::Fixed(fixed_width)) => {
435 let mut node = KdlNode::new("width");
436 node.entries_mut()
437 .push(KdlEntry::new(KdlValue::Base10(fixed_width as i64)));
438 pane_node_children.nodes_mut().push(node);
439 },
440 Some(PercentOrFixed::Percent(percent)) => {
441 let mut node = KdlNode::new("width");
442 node.entries_mut()
443 .push(KdlEntry::new(format!("{}%", percent)));
444 pane_node_children.nodes_mut().push(node);
445 },
446 None => {},
447 }
448 match layout.x {
449 Some(PercentOrFixed::Fixed(fixed_x)) => {
450 let mut node = KdlNode::new("x");
451 node.entries_mut()
452 .push(KdlEntry::new(KdlValue::Base10(fixed_x as i64)));
453 pane_node_children.nodes_mut().push(node);
454 },
455 Some(PercentOrFixed::Percent(percent)) => {
456 let mut node = KdlNode::new("x");
457 node.entries_mut()
458 .push(KdlEntry::new(format!("{}%", percent)));
459 pane_node_children.nodes_mut().push(node);
460 },
461 None => {},
462 }
463 match layout.y {
464 Some(PercentOrFixed::Fixed(fixed_y)) => {
465 let mut node = KdlNode::new("y");
466 node.entries_mut()
467 .push(KdlEntry::new(KdlValue::Base10(fixed_y as i64)));
468 pane_node_children.nodes_mut().push(node);
469 },
470 Some(PercentOrFixed::Percent(percent)) => {
471 let mut node = KdlNode::new("y");
472 node.entries_mut()
473 .push(KdlEntry::new(format!("{}%", percent)));
474 pane_node_children.nodes_mut().push(node);
475 },
476 None => {},
477 }
478 match layout.pinned {
479 Some(true) => {
480 let mut node = KdlNode::new("pinned");
481 node.entries_mut().push(KdlEntry::new(KdlValue::Bool(true)));
482 pane_node_children.nodes_mut().push(node);
483 },
484 _ => {},
485 }
486}
487
488fn serialize_start_suspended(command: &Option<String>, pane_node_children: &mut KdlDocument) {
489 if command.is_some() {
490 let mut start_suspended_node = KdlNode::new("start_suspended");
491 start_suspended_node
492 .entries_mut()
493 .push(KdlEntry::new(KdlValue::Bool(true)));
494 pane_node_children.nodes_mut().push(start_suspended_node);
495 }
496}
497
498fn serialize_global_cwd(global_cwd: &Option<PathBuf>) -> Option<KdlNode> {
499 global_cwd.as_ref().map(|cwd| {
500 let mut node = KdlNode::new("cwd");
501 node.push(cwd.display().to_string());
502 node
503 })
504}
505
506fn serialize_new_tab_template(
507 new_tab_template: Option<(TiledPaneLayout, Vec<FloatingPaneLayout>)>,
508 pane_contents: &mut BTreeMap<String, String>,
509 layout_children_node: &mut KdlDocument,
510) {
511 if let Some((tiled_panes, floating_panes)) = new_tab_template {
512 let tiled_panes = tiled_panes_to_serialize(tiled_panes);
513 let mut new_tab_template_node = KdlNode::new("new_tab_template");
514 let mut new_tab_template_children = KdlDocument::new();
515
516 serialize_tiled_and_floating_panes(
517 &tiled_panes,
518 floating_panes,
519 pane_contents,
520 &mut new_tab_template_children,
521 );
522 new_tab_template_node.set_children(new_tab_template_children);
523 layout_children_node.nodes_mut().push(new_tab_template_node);
524 }
525}
526
527fn serialize_swap_tiled_layouts(
528 swap_tiled_layouts: Vec<SwapTiledLayout>,
529 pane_contents: &mut BTreeMap<String, String>,
530 layout_node_children: &mut KdlDocument,
531) {
532 for swap_tiled_layout in swap_tiled_layouts {
533 let mut swap_tiled_layout_node = KdlNode::new("swap_tiled_layout");
534 let mut swap_tiled_layout_node_children = KdlDocument::new();
535 let swap_tiled_layout_name = swap_tiled_layout.1;
536 if let Some(name) = swap_tiled_layout_name {
537 swap_tiled_layout_node
538 .entries_mut()
539 .push(KdlEntry::new_prop("name", name.to_owned()));
540 }
541
542 for (layout_constraint, tiled_panes_layout) in swap_tiled_layout.0 {
543 let tiled_panes_layout = tiled_panes_to_serialize(tiled_panes_layout);
544 let mut layout_step_node = KdlNode::new("tab");
545 let mut layout_step_node_children = KdlDocument::new();
546 if let Some(layout_constraint_entry) = serialize_layout_constraint(layout_constraint) {
547 layout_step_node.entries_mut().push(layout_constraint_entry);
548 }
549
550 serialize_tiled_and_floating_panes(
551 &tiled_panes_layout,
552 vec![],
553 pane_contents,
554 &mut layout_step_node_children,
555 );
556 layout_step_node.set_children(layout_step_node_children);
557 swap_tiled_layout_node_children
558 .nodes_mut()
559 .push(layout_step_node);
560 }
561 swap_tiled_layout_node.set_children(swap_tiled_layout_node_children);
562 layout_node_children
563 .nodes_mut()
564 .push(swap_tiled_layout_node);
565 }
566}
567
568fn serialize_layout_constraint(layout_constraint: LayoutConstraint) -> Option<KdlEntry> {
569 match layout_constraint {
570 LayoutConstraint::MaxPanes(max_panes) => Some(KdlEntry::new_prop(
571 "max_panes",
572 KdlValue::Base10(max_panes as i64),
573 )),
574 LayoutConstraint::MinPanes(min_panes) => Some(KdlEntry::new_prop(
575 "min_panes",
576 KdlValue::Base10(min_panes as i64),
577 )),
578 LayoutConstraint::ExactPanes(exact_panes) => Some(KdlEntry::new_prop(
579 "exact_panes",
580 KdlValue::Base10(exact_panes as i64),
581 )),
582 LayoutConstraint::NoConstraint => None,
583 }
584}
585
586fn serialize_swap_floating_layouts(
587 swap_floating_layouts: Vec<SwapFloatingLayout>,
588 pane_contents: &mut BTreeMap<String, String>,
589 layout_children_node: &mut KdlDocument,
590) {
591 for swap_floating_layout in swap_floating_layouts {
592 let mut swap_floating_layout_node = KdlNode::new("swap_floating_layout");
593 let mut swap_floating_layout_node_children = KdlDocument::new();
594 let swap_floating_layout_name = swap_floating_layout.1;
595 if let Some(name) = swap_floating_layout_name {
596 swap_floating_layout_node
597 .entries_mut()
598 .push(KdlEntry::new_prop("name", name.to_owned()));
599 }
600
601 for (layout_constraint, floating_panes_layout) in swap_floating_layout.0 {
602 let mut layout_step_node = KdlNode::new("floating_panes");
603 let mut layout_step_node_children = KdlDocument::new();
604 if let Some(layout_constraint_entry) = serialize_layout_constraint(layout_constraint) {
605 layout_step_node.entries_mut().push(layout_constraint_entry);
606 }
607
608 for floating_pane_layout in floating_panes_layout {
609 let floating_pane_node =
610 serialize_floating_pane(&floating_pane_layout, pane_contents);
611 layout_step_node_children
612 .nodes_mut()
613 .push(floating_pane_node);
614 }
615 layout_step_node.set_children(layout_step_node_children);
616 swap_floating_layout_node_children
617 .nodes_mut()
618 .push(layout_step_node);
619 }
620 swap_floating_layout_node.set_children(swap_floating_layout_node_children);
621 layout_children_node
622 .nodes_mut()
623 .push(swap_floating_layout_node);
624 }
625}
626
627fn serialize_multiple_tabs(
628 tabs: Vec<(String, TabLayoutManifest)>,
629 pane_contents: &mut BTreeMap<String, String>,
630) -> Result<Vec<KdlNode>, &'static str> {
631 let mut serialized_tabs: Vec<KdlNode> = vec![];
632 for (tab_name, tab_layout_manifest) in tabs {
633 let tiled_panes = tab_layout_manifest.tiled_panes;
634 let floating_panes = tab_layout_manifest.floating_panes;
635 let hide_floating_panes = tab_layout_manifest.hide_floating_panes;
636 let serialized = serialize_tab(
637 tab_name.clone(),
638 tab_layout_manifest.is_focused,
639 hide_floating_panes,
640 &tiled_panes,
641 &floating_panes,
642 pane_contents,
643 );
644 if let Some(serialized) = serialized {
645 serialized_tabs.push(serialized);
646 } else {
647 return Err("Failed to serialize session state");
648 }
649 }
650 Ok(serialized_tabs)
651}
652
653fn serialize_floating_pane(
654 layout: &FloatingPaneLayout,
655 pane_contents: &mut BTreeMap<String, String>,
656) -> KdlNode {
657 let mut floating_pane_node = KdlNode::new("pane");
658 let mut floating_pane_node_children = KdlDocument::new();
659 let (command, args) = extract_command_and_args(&layout.run);
660 let (plugin, plugin_config) = extract_plugin_and_config(&layout.run);
661 let (edit, _line_number) = extract_edit_and_line_number(&layout.run);
662 let cwd = layout.run.as_ref().and_then(|r| r.get_cwd());
663 let has_children = false;
664 serialize_pane_title_and_attributes(
665 &command,
666 &edit,
667 &layout.name,
668 cwd,
669 layout.focus,
670 &layout.pane_initial_contents,
671 pane_contents,
672 has_children,
673 &mut floating_pane_node,
674 );
675 if let Some(ref fg) = layout.default_fg {
676 floating_pane_node
677 .entries_mut()
678 .push(KdlEntry::new_prop("default_fg", fg.to_owned()));
679 }
680 if let Some(ref bg) = layout.default_bg {
681 floating_pane_node
682 .entries_mut()
683 .push(KdlEntry::new_prop("default_bg", bg.to_owned()));
684 }
685 serialize_start_suspended(&command, &mut floating_pane_node_children);
686 serialize_floating_layout_attributes(&layout, &mut floating_pane_node_children);
687 serialize_args(args, &mut floating_pane_node_children);
688 serialize_plugin(plugin, plugin_config, &mut floating_pane_node_children);
689 floating_pane_node.set_children(floating_pane_node_children);
690 floating_pane_node
691}
692
693fn stack_layout_from_manifest(
694 geoms: &Vec<PaneLayoutManifest>,
695 split_size: Option<SplitSize>,
696) -> Option<TiledPaneLayout> {
697 let mut children_stacks: HashMap<usize, Vec<PaneLayoutManifest>> = HashMap::new();
698 for p in geoms {
699 if let Some(stack_id) = p.geom.stacked {
700 children_stacks
701 .entry(stack_id)
702 .or_insert_with(Default::default)
703 .push(p.clone());
704 }
705 }
706 let mut stack_nodes = vec![];
707 for (_stack_id, mut stacked_panes) in children_stacks.into_iter() {
708 stacked_panes.sort_by_key(|p| p.geom.y);
709 stack_nodes.push(TiledPaneLayout {
710 split_size,
711 children: stacked_panes
712 .iter()
713 .map(|p| tiled_pane_layout_from_manifest(Some(p), None))
714 .collect(),
715 children_are_stacked: true,
716 ..Default::default()
717 })
718 }
719 if stack_nodes.len() == 1 {
720 stack_nodes.iter().next().cloned()
722 } else {
723 Some(TiledPaneLayout {
725 split_size,
726 children: stack_nodes,
727 ..Default::default()
728 })
729 }
730}
731
732fn tiled_pane_layout_from_manifest(
733 manifest: Option<&PaneLayoutManifest>,
734 split_size: Option<SplitSize>,
735) -> TiledPaneLayout {
736 let (
737 run,
738 borderless,
739 is_expanded_in_stack,
740 name,
741 focus,
742 pane_initial_contents,
743 default_fg,
744 default_bg,
745 ) = manifest
746 .map(|g| {
747 let mut run = g.run.clone();
748 if let Some(cwd) = &g.cwd {
749 if let Some(run) = run.as_mut() {
750 run.add_cwd(cwd);
751 } else {
752 run = Some(Run::Cwd(cwd.clone()));
753 }
754 }
755 (
756 run,
757 Some(g.is_borderless),
758 g.geom.is_stacked() && g.geom.rows.inner > 1,
759 g.title.clone(),
760 Some(g.is_focused),
761 g.pane_contents.clone(),
762 g.default_fg.clone(),
763 g.default_bg.clone(),
764 )
765 })
766 .unwrap_or((None, None, false, None, None, None, None, None));
767 TiledPaneLayout {
768 split_size,
769 run,
770 borderless,
771 is_expanded_in_stack,
772 name,
773 focus,
774 pane_initial_contents,
775 default_fg,
776 default_bg,
777 ..Default::default()
778 }
779}
780
781fn get_tiled_panes_layout_from_panegeoms(
783 geoms: &Vec<PaneLayoutManifest>,
784 split_size: Option<SplitSize>,
785) -> Option<TiledPaneLayout> {
786 let (children_split_direction, splits) = match get_splits(&geoms) {
787 Some(x) => x,
788 None => {
789 if geoms.len() > 1 {
790 return stack_layout_from_manifest(geoms, split_size);
793 } else {
794 return Some(tiled_pane_layout_from_manifest(
795 geoms.iter().next(),
796 split_size,
797 ));
798 }
799 },
800 };
801 let mut children = Vec::new();
802 let mut remaining_geoms = geoms.clone();
803 let mut new_geoms = Vec::new();
804 let mut new_constraints = Vec::new();
805 for i in 1..splits.len() {
806 let (v_min, v_max) = (splits[i - 1], splits[i]);
807 let subgeoms: Vec<PaneLayoutManifest>;
808 (subgeoms, remaining_geoms) = match children_split_direction {
809 SplitDirection::Horizontal => remaining_geoms
810 .clone()
811 .into_iter()
812 .partition(|g| g.geom.y + g.geom.rows.as_usize() <= v_max),
813 SplitDirection::Vertical => remaining_geoms
814 .clone()
815 .into_iter()
816 .partition(|g| g.geom.x + g.geom.cols.as_usize() <= v_max),
817 };
818 match get_domain_constraint(&subgeoms, &children_split_direction, (v_min, v_max)) {
819 Some(constraint) => {
820 new_geoms.push(subgeoms);
821 new_constraints.push(constraint);
822 },
823 None => {
824 return None;
825 },
826 }
827 }
828
829 let new_split_sizes = get_split_sizes(&new_constraints);
830
831 for (subgeoms, subsplit_size) in new_geoms.iter().zip(new_split_sizes) {
832 match get_tiled_panes_layout_from_panegeoms(&subgeoms, subsplit_size) {
833 Some(child) => {
834 children.push(child);
835 },
836 None => {
837 return None;
838 },
839 }
840 }
841 let children_are_stacked = children_split_direction == SplitDirection::Horizontal
842 && all_geoms_are_from_the_same_stack(&new_geoms);
843 Some(TiledPaneLayout {
844 children_split_direction,
845 split_size,
846 children,
847 children_are_stacked,
848 ..Default::default()
849 })
850}
851
852fn all_geoms_are_from_the_same_stack(manifests: &Vec<Vec<PaneLayoutManifest>>) -> bool {
853 let mut stack_ids = HashSet::new();
854 for manifest_group in manifests {
855 for pane_layout_manifest in manifest_group {
856 stack_ids.insert(pane_layout_manifest.geom.stacked);
857 }
858 }
859 stack_ids.len() == 1 && !stack_ids.contains(&None)
860}
861
862fn get_floating_panes_layout_from_panegeoms(
863 manifests: &Vec<PaneLayoutManifest>,
864) -> Vec<FloatingPaneLayout> {
865 manifests
866 .iter()
867 .map(|m| {
868 let mut run = m.run.clone();
869 if let Some(cwd) = &m.cwd {
870 run.as_mut().map(|r| r.add_cwd(cwd));
871 }
872 FloatingPaneLayout {
873 name: m.title.clone(),
874 height: Some(m.geom.rows.into()),
875 width: Some(m.geom.cols.into()),
876 x: Some(PercentOrFixed::Fixed(m.geom.x)),
877 y: Some(PercentOrFixed::Fixed(m.geom.y)),
878 pinned: Some(m.geom.is_pinned),
879 run,
880 focus: Some(m.is_focused),
881 already_running: false,
882 pane_initial_contents: m.pane_contents.clone(),
883 logical_position: None,
884 borderless: Some(m.is_borderless),
885 default_fg: m.default_fg.clone(),
886 default_bg: m.default_bg.clone(),
887 }
888 })
889 .collect()
890}
891
892fn get_x_lims(geoms: &Vec<PaneLayoutManifest>) -> Option<(usize, usize)> {
893 match (
894 geoms.iter().map(|g| g.geom.x).min(),
895 geoms
896 .iter()
897 .map(|g| g.geom.x + g.geom.cols.as_usize())
898 .max(),
899 ) {
900 (Some(x_min), Some(x_max)) => Some((x_min, x_max)),
901 _ => None,
902 }
903}
904
905fn get_y_lims(geoms: &Vec<PaneLayoutManifest>) -> Option<(usize, usize)> {
906 match (
907 geoms.iter().map(|g| g.geom.y).min(),
908 geoms
909 .iter()
910 .map(|g| g.geom.y + g.geom.rows.as_usize())
911 .max(),
912 ) {
913 (Some(y_min), Some(y_max)) => Some((y_min, y_max)),
914 _ => None,
915 }
916}
917
918fn get_splits(geoms: &Vec<PaneLayoutManifest>) -> Option<(SplitDirection, Vec<usize>)> {
923 if geoms.len() == 1 {
924 return None;
925 }
926 let (x_lims, y_lims) = match (get_x_lims(&geoms), get_y_lims(&geoms)) {
927 (Some(x_lims), Some(y_lims)) => (x_lims, y_lims),
928 _ => return None,
929 };
930 let mut direction = SplitDirection::default();
931 let mut splits = match direction {
932 SplitDirection::Vertical => get_col_splits(&geoms, &x_lims, &y_lims),
933 SplitDirection::Horizontal => get_row_splits(&geoms, &x_lims, &y_lims),
934 };
935 if splits.len() <= 2 {
936 direction = !direction;
938 splits = match direction {
939 SplitDirection::Vertical => get_col_splits(&geoms, &x_lims, &y_lims),
940 SplitDirection::Horizontal => get_row_splits(&geoms, &x_lims, &y_lims),
941 };
942 }
943 if splits.len() <= 2 {
944 None
946 } else {
947 Some((direction, splits))
948 }
949}
950
951fn get_col_splits(
954 geoms: &Vec<PaneLayoutManifest>,
955 (_, x_max): &(usize, usize),
956 (y_min, y_max): &(usize, usize),
957) -> Vec<usize> {
958 let max_rows = y_max - y_min;
959 let mut splits = Vec::new();
960 let mut sorted_geoms = geoms.clone();
961 sorted_geoms.sort_by_key(|g| g.geom.x);
962 for x in sorted_geoms.iter().map(|g| g.geom.x) {
963 if splits.contains(&x) {
964 continue;
965 }
966 if sorted_geoms
967 .iter()
968 .filter(|g| g.geom.x == x)
969 .map(|g| g.geom.rows.as_usize())
970 .sum::<usize>()
971 == max_rows
972 {
973 splits.push(x);
974 };
975 }
976 splits.push(*x_max); splits
978}
979
980fn get_row_splits(
983 geoms: &Vec<PaneLayoutManifest>,
984 (x_min, x_max): &(usize, usize),
985 (_, y_max): &(usize, usize),
986) -> Vec<usize> {
987 let max_cols = x_max - x_min;
988 let mut splits = Vec::new();
989 let mut sorted_geoms = geoms.clone();
990 sorted_geoms.sort_by_key(|g| g.geom.y);
991
992 let mut stack_geoms: HashMap<usize, Vec<PaneLayoutManifest>> = HashMap::new();
996 let mut all_geoms = vec![];
997 for pane_layout_manifest in sorted_geoms.drain(..) {
998 if let Some(stack_id) = pane_layout_manifest.geom.stacked {
999 stack_geoms
1000 .entry(stack_id)
1001 .or_insert_with(Default::default)
1002 .push(pane_layout_manifest)
1003 } else {
1004 all_geoms.push(pane_layout_manifest);
1005 }
1006 }
1007 for (_stack_id, mut geoms_in_stack) in stack_geoms.into_iter() {
1008 let mut geom_of_whole_stack = geoms_in_stack.remove(0);
1009 if let Some(last_geom) = geoms_in_stack.last() {
1010 geom_of_whole_stack
1011 .geom
1012 .rows
1013 .set_inner(last_geom.geom.y + last_geom.geom.rows.as_usize())
1014 }
1015 all_geoms.push(geom_of_whole_stack);
1016 }
1017
1018 all_geoms.sort_by_key(|g| g.geom.y);
1019
1020 for y in all_geoms.iter().map(|g| g.geom.y) {
1021 if splits.contains(&y) {
1022 continue;
1023 }
1024 if all_geoms
1025 .iter()
1026 .filter(|g| g.geom.y == y)
1027 .map(|g| g.geom.cols.as_usize())
1028 .sum::<usize>()
1029 == max_cols
1030 {
1031 splits.push(y);
1032 };
1033 }
1034 splits.push(*y_max); splits
1036}
1037
1038fn get_domain_constraint(
1041 geoms: &Vec<PaneLayoutManifest>,
1042 split_direction: &SplitDirection,
1043 (v_min, v_max): (usize, usize),
1044) -> Option<Constraint> {
1045 match split_direction {
1046 SplitDirection::Horizontal => get_domain_row_constraint(&geoms, (v_min, v_max)),
1047 SplitDirection::Vertical => get_domain_col_constraint(&geoms, (v_min, v_max)),
1048 }
1049}
1050
1051fn get_domain_col_constraint(
1052 geoms: &Vec<PaneLayoutManifest>,
1053 (x_min, x_max): (usize, usize),
1054) -> Option<Constraint> {
1055 let mut percent = 0.0;
1056 let mut x = x_min;
1057 while x != x_max {
1058 let geom = geoms.iter().filter(|g| g.geom.x == x).last();
1060 match geom {
1061 Some(geom) => {
1062 if let Some(size) = geom.geom.cols.as_percent() {
1063 percent += size;
1064 }
1065 x += geom.geom.cols.as_usize();
1066 },
1067 None => {
1068 return None;
1069 },
1070 }
1071 }
1072 if percent == 0.0 {
1073 Some(Constraint::Fixed(x_max - x_min))
1074 } else {
1075 Some(Constraint::Percent(percent))
1076 }
1077}
1078
1079fn get_domain_row_constraint(
1080 geoms: &Vec<PaneLayoutManifest>,
1081 (y_min, y_max): (usize, usize),
1082) -> Option<Constraint> {
1083 let mut percent = 0.0;
1084 let mut y = y_min;
1085 while y != y_max {
1086 let geom = geoms.iter().filter(|g| g.geom.y == y).last();
1088 match geom {
1089 Some(geom) => {
1090 if let Some(size) = geom.geom.rows.as_percent() {
1091 percent += size;
1092 }
1093 y += geom.geom.rows.as_usize();
1094 },
1095 None => {
1096 return None;
1097 },
1098 }
1099 }
1100 if percent == 0.0 {
1101 Some(Constraint::Fixed(y_max - y_min))
1102 } else {
1103 Some(Constraint::Percent(percent))
1104 }
1105}
1106
1107fn get_split_sizes(constraints: &Vec<Constraint>) -> Vec<Option<SplitSize>> {
1110 let mut split_sizes = Vec::new();
1111 let max_percent = constraints
1112 .iter()
1113 .filter_map(|c| match c {
1114 Constraint::Percent(size) => Some(size),
1115 _ => None,
1116 })
1117 .sum::<f64>();
1118 for constraint in constraints {
1119 let split_size = match constraint {
1120 Constraint::Fixed(size) => Some(SplitSize::Fixed(*size)),
1121 Constraint::Percent(size) => {
1122 if size == &max_percent {
1123 None
1124 } else {
1125 Some(SplitSize::Percent((100.0 * size / max_percent) as usize))
1126 }
1127 },
1128 };
1129 split_sizes.push(split_size);
1130 }
1131 split_sizes
1132}
1133
1134#[cfg(test)]
1135mod tests {
1136
1137 use super::*;
1138 use crate::pane_size::Dimension;
1139 use expect_test::expect;
1140 use insta::assert_snapshot;
1141 use serde_json::Value;
1142 use std::collections::HashMap;
1143 const PANEGEOMS_JSON: &[&[&str]] = &[
1144 &[
1145 r#"{ "x": 0, "y": 1, "rows": { "constraint": "Percent(100.0)", "inner": 43 }, "cols": { "constraint": "Percent(100.0)", "inner": 211 }, "is_stacked": false }"#,
1146 r#"{ "x": 0, "y": 0, "rows": { "constraint": "Fixed(1)", "inner": 1 }, "cols": { "constraint": "Percent(100.0)", "inner": 211 }, "is_stacked": false }"#,
1147 r#"{ "x": 0, "y": 44, "rows": { "constraint": "Fixed(2)", "inner": 2 }, "cols": { "constraint": "Percent(100.0)", "inner": 211 }, "is_stacked": false }"#,
1148 ],
1149 &[
1150 r#"{ "x": 0, "y": 0, "rows": { "constraint": "Percent(100.0)", "inner": 26 }, "cols": { "constraint": "Percent(100.0)", "inner": 211 }, "is_stacked": false }"#,
1151 r#"{ "x": 0, "y": 26, "rows": { "constraint": "Fixed(20)", "inner": 20 }, "cols": { "constraint": "Fixed(50)", "inner": 50 }, "is_stacked": false }"#,
1152 r#"{ "x": 50, "y": 26, "rows": { "constraint": "Fixed(20)", "inner": 20 }, "cols": { "constraint": "Percent(100.0)", "inner": 161 }, "is_stacked": false }"#,
1153 ],
1154 &[
1155 r#"{ "x": 0, "y": 0, "rows": { "constraint": "Fixed(10)", "inner": 10 }, "cols": { "constraint": "Percent(50.0)", "inner": 106 }, "is_stacked": false }"#,
1156 r#"{ "x": 106, "y": 0, "rows": { "constraint": "Fixed(10)", "inner": 10 }, "cols": { "constraint": "Percent(50.0)", "inner": 105 }, "is_stacked": false }"#,
1157 r#"{ "x": 0, "y": 10, "rows": { "constraint": "Percent(100.0)", "inner": 26 }, "cols": { "constraint": "Fixed(40)", "inner": 40 }, "is_stacked": false }"#,
1158 r#"{ "x": 40, "y": 10, "rows": { "constraint": "Percent(100.0)", "inner": 26 }, "cols": { "constraint": "Percent(100.0)", "inner": 131 }, "is_stacked": false }"#,
1159 r#"{ "x": 171, "y": 10, "rows": { "constraint": "Percent(100.0)", "inner": 26 }, "cols": { "constraint": "Fixed(40)", "inner": 40 }, "is_stacked": false }"#,
1160 r#"{ "x": 0, "y": 36, "rows": { "constraint": "Fixed(10)", "inner": 10 }, "cols": { "constraint": "Percent(50.0)", "inner": 106 }, "is_stacked": false }"#,
1161 r#"{ "x": 106, "y": 36, "rows": { "constraint": "Fixed(10)", "inner": 10 }, "cols": { "constraint": "Percent(50.0)", "inner": 105 }, "is_stacked": false }"#,
1162 ],
1163 &[
1164 r#"{ "x": 0, "y": 0, "rows": { "constraint": "Percent(30.0)", "inner": 11 }, "cols": { "constraint": "Percent(35.0)", "inner": 74 }, "is_stacked": false }"#,
1165 r#"{ "x": 0, "y": 11, "rows": { "constraint": "Percent(30.0)", "inner": 11 }, "cols": { "constraint": "Percent(35.0)", "inner": 74 }, "is_stacked": false }"#,
1166 r#"{ "x": 0, "y": 22, "rows": { "constraint": "Percent(40.0)", "inner": 14 }, "cols": { "constraint": "Percent(35.0)", "inner": 74 }, "is_stacked": false }"#,
1167 r#"{ "x": 74, "y": 0, "rows": { "constraint": "Percent(100.0)", "inner": 36 }, "cols": { "constraint": "Percent(35.0)", "inner": 74 }, "is_stacked": false }"#,
1168 r#"{ "x": 0, "y": 36, "rows": { "constraint": "Fixed(10)", "inner": 10 }, "cols": { "constraint": "Percent(70.0)", "inner": 148 }, "is_stacked": false }"#,
1169 r#"{ "x": 148, "y": 0, "rows": { "constraint": "Percent(100.0)", "inner": 46 }, "cols": { "constraint": "Percent(30.0)", "inner": 63 }, "is_stacked": false }"#,
1170 ],
1171 &[
1172 r#"{ "x": 0, "y": 0, "rows": { "constraint": "Fixed(5)", "inner": 5 }, "cols": { "constraint": "Percent(100.0)", "inner": 211 }, "is_stacked": false }"#,
1173 r#"{ "x": 0, "y": 5, "rows": { "constraint": "Percent(100.0)", "inner": 36 }, "cols": { "constraint": "Fixed(20)", "inner": 20 }, "is_stacked": false }"#,
1174 r#"{ "x": 20, "y": 5, "rows": { "constraint": "Percent(100.0)", "inner": 36 }, "cols": { "constraint": "Percent(50.0)", "inner": 86 }, "is_stacked": false }"#,
1175 r#"{ "x": 106, "y": 5, "rows": { "constraint": "Percent(100.0)", "inner": 36 }, "cols": { "constraint": "Percent(50.0)", "inner": 85 }, "is_stacked": false }"#,
1176 r#"{ "x": 191, "y": 5, "rows": { "constraint": "Percent(100.0)", "inner": 36 }, "cols": { "constraint": "Fixed(20)", "inner": 20 }, "is_stacked": false }"#,
1177 r#"{ "x": 0, "y": 41, "rows": { "constraint": "Fixed(5)", "inner": 5 }, "cols": { "constraint": "Percent(100.0)", "inner": 211 }, "is_stacked": false }"#,
1178 ],
1179 ];
1180
1181 #[test]
1182 fn geoms() {
1183 let geoms = PANEGEOMS_JSON[0]
1184 .iter()
1185 .map(|pg| parse_panegeom_from_json(pg))
1186 .map(|geom| PaneLayoutManifest {
1187 geom,
1188 ..Default::default()
1189 })
1190 .collect();
1191 let tab_layout_manifest = TabLayoutManifest {
1192 tiled_panes: geoms,
1193 ..Default::default()
1194 };
1195 let global_layout_manifest = GlobalLayoutManifest {
1196 tabs: vec![("Tab #1".to_owned(), tab_layout_manifest)],
1197 ..Default::default()
1198 };
1199 let kdl = serialize_session_layout(global_layout_manifest).unwrap();
1200 expect![[r#"
1201 layout {
1202 tab name="Tab #1" {
1203 pane size=1
1204 pane
1205 pane size=2
1206 }
1207 }
1208 "#]]
1209 .assert_eq(&kdl.0);
1210
1211 let geoms = PANEGEOMS_JSON[1]
1212 .iter()
1213 .map(|pg| parse_panegeom_from_json(pg))
1214 .map(|geom| PaneLayoutManifest {
1215 geom,
1216 ..Default::default()
1217 })
1218 .collect();
1219 let tab_layout_manifest = TabLayoutManifest {
1220 tiled_panes: geoms,
1221 ..Default::default()
1222 };
1223 let global_layout_manifest = GlobalLayoutManifest {
1224 tabs: vec![("Tab #1".to_owned(), tab_layout_manifest)],
1225 ..Default::default()
1226 };
1227 let kdl = serialize_session_layout(global_layout_manifest).unwrap();
1228 expect![[r#"
1229 layout {
1230 tab name="Tab #1" {
1231 pane
1232 pane size=20 split_direction="vertical" {
1233 pane size=50
1234 pane
1235 }
1236 }
1237 }
1238 "#]]
1239 .assert_eq(&kdl.0);
1240
1241 let geoms = PANEGEOMS_JSON[2]
1242 .iter()
1243 .map(|pg| parse_panegeom_from_json(pg))
1244 .map(|geom| PaneLayoutManifest {
1245 geom,
1246 ..Default::default()
1247 })
1248 .collect();
1249 let tab_layout_manifest = TabLayoutManifest {
1250 tiled_panes: geoms,
1251 ..Default::default()
1252 };
1253 let global_layout_manifest = GlobalLayoutManifest {
1254 tabs: vec![("Tab #1".to_owned(), tab_layout_manifest)],
1255 ..Default::default()
1256 };
1257 let kdl = serialize_session_layout(global_layout_manifest).unwrap();
1258 expect![[r#"
1259 layout {
1260 tab name="Tab #1" {
1261 pane size=10 split_direction="vertical" {
1262 pane size="50%"
1263 pane size="50%"
1264 }
1265 pane split_direction="vertical" {
1266 pane size=40
1267 pane
1268 pane size=40
1269 }
1270 pane size=10 split_direction="vertical" {
1271 pane size="50%"
1272 pane size="50%"
1273 }
1274 }
1275 }
1276 "#]]
1277 .assert_eq(&kdl.0);
1278
1279 let geoms = PANEGEOMS_JSON[3]
1280 .iter()
1281 .map(|pg| parse_panegeom_from_json(pg))
1282 .map(|geom| PaneLayoutManifest {
1283 geom,
1284 ..Default::default()
1285 })
1286 .collect();
1287 let tab_layout_manifest = TabLayoutManifest {
1288 tiled_panes: geoms,
1289 ..Default::default()
1290 };
1291 let global_layout_manifest = GlobalLayoutManifest {
1292 tabs: vec![("Tab #1".to_owned(), tab_layout_manifest)],
1293 ..Default::default()
1294 };
1295 let kdl = serialize_session_layout(global_layout_manifest).unwrap();
1296 expect![[r#"
1297 layout {
1298 tab name="Tab #1" {
1299 pane split_direction="vertical" {
1300 pane size="70%" {
1301 pane split_direction="vertical" {
1302 pane size="50%" {
1303 pane size="30%"
1304 pane size="30%"
1305 pane size="40%"
1306 }
1307 pane size="50%"
1308 }
1309 pane size=10
1310 }
1311 pane size="30%"
1312 }
1313 }
1314 }
1315 "#]]
1316 .assert_eq(&kdl.0);
1317
1318 let geoms = PANEGEOMS_JSON[4]
1319 .iter()
1320 .map(|pg| parse_panegeom_from_json(pg))
1321 .map(|geom| PaneLayoutManifest {
1322 geom,
1323 ..Default::default()
1324 })
1325 .collect();
1326 let tab_layout_manifest = TabLayoutManifest {
1327 tiled_panes: geoms,
1328 ..Default::default()
1329 };
1330 let global_layout_manifest = GlobalLayoutManifest {
1331 tabs: vec![("Tab #1".to_owned(), tab_layout_manifest)],
1332 ..Default::default()
1333 };
1334 let kdl = serialize_session_layout(global_layout_manifest).unwrap();
1335 expect![[r#"
1336 layout {
1337 tab name="Tab #1" {
1338 pane size=5
1339 pane split_direction="vertical" {
1340 pane size=20
1341 pane size="50%"
1342 pane size="50%"
1343 pane size=20
1344 }
1345 pane size=5
1346 }
1347 }
1348 "#]]
1349 .assert_eq(&kdl.0);
1350 }
1351
1352 #[test]
1353 fn global_cwd() {
1354 let global_layout_manifest = GlobalLayoutManifest {
1355 global_cwd: Some(PathBuf::from("/path/to/m\"y/global cwd")),
1356 ..Default::default()
1357 };
1358 let kdl = serialize_session_layout(global_layout_manifest).unwrap();
1359 assert_snapshot!(kdl.0);
1360 }
1361
1362 #[test]
1363 fn can_serialize_tab_with_a_single_tiled_pane() {
1364 use crate::input::command::RunCommand;
1365 let tab_layout_manifest = TabLayoutManifest {
1366 tiled_panes: vec![PaneLayoutManifest {
1367 run: Some(Run::Command(RunCommand {
1368 command: PathBuf::from("/bin/sleep"),
1369 args: vec!["10000".to_owned()],
1370 ..Default::default()
1371 })),
1372 title: Some("my-only-pane".to_owned()),
1373 geom: PaneGeom {
1374 x: 0,
1375 y: 0,
1376 rows: Dimension::fixed(10),
1377 cols: Dimension::fixed(10),
1378 stacked: None,
1379 is_pinned: false,
1380 logical_position: None,
1381 },
1382 ..Default::default()
1383 }],
1384 ..Default::default()
1385 };
1386 let global_layout_manifest = GlobalLayoutManifest {
1387 tabs: vec![("Tab #1".to_owned(), tab_layout_manifest)],
1388 ..Default::default()
1389 };
1390 let kdl = serialize_session_layout(global_layout_manifest).unwrap();
1391 expect![[r#"
1392 layout {
1393 tab name="Tab #1" {
1394 pane command="/bin/sleep" name="my-only-pane" {
1395 args "10000"
1396 start_suspended true
1397 }
1398 }
1399 }
1400 "#]]
1401 .assert_eq(&kdl.0);
1402 }
1403
1404 #[test]
1405 fn can_serialize_tab_with_a_single_tiled_pane_and_a_floating_pane() {
1406 let tab_layout_manifest = TabLayoutManifest {
1407 tiled_panes: vec![PaneLayoutManifest {
1408 title: Some("my-only-tiled-pane".to_owned()),
1409 geom: PaneGeom {
1410 x: 0,
1411 y: 0,
1412 rows: Dimension::fixed(10),
1413 cols: Dimension::fixed(10),
1414 stacked: None,
1415 is_pinned: false,
1416 logical_position: None,
1417 },
1418 ..Default::default()
1419 }],
1420 floating_panes: vec![PaneLayoutManifest {
1421 title: Some("my-floating-pane".to_owned()),
1422 geom: PaneGeom {
1423 x: 1,
1424 y: 1,
1425 rows: Dimension::fixed(5),
1426 cols: Dimension::fixed(5),
1427 stacked: None,
1428 is_pinned: false,
1429 logical_position: None,
1430 },
1431 ..Default::default()
1432 }],
1433 ..Default::default()
1434 };
1435 let global_layout_manifest = GlobalLayoutManifest {
1436 tabs: vec![("Tab #1".to_owned(), tab_layout_manifest)],
1437 ..Default::default()
1438 };
1439 let kdl = serialize_session_layout(global_layout_manifest).unwrap();
1440 expect![[r#"
1441 layout {
1442 tab name="Tab #1" {
1443 pane name="my-only-tiled-pane"
1444 floating_panes {
1445 pane name="my-floating-pane" {
1446 height 5
1447 width 5
1448 x 1
1449 y 1
1450 }
1451 }
1452 }
1453 }
1454 "#]]
1455 .assert_eq(&kdl.0);
1456 }
1457
1458 #[test]
1459 fn can_serialize_new_tab_template_with_a_single_pane() {
1460 let tiled_panes_layout = TiledPaneLayout {
1461 name: Some("my-only-template-pane".to_owned()),
1462 ..Default::default()
1463 };
1464 let mut default_layout = Layout::default();
1465 default_layout.template = Some((tiled_panes_layout, vec![]));
1466 let global_layout_manifest = GlobalLayoutManifest {
1467 default_layout: Box::new(default_layout),
1468 ..Default::default()
1469 };
1470 let kdl = serialize_session_layout(global_layout_manifest).unwrap();
1471 expect![[r#"
1472 layout {
1473 new_tab_template {
1474 pane name="my-only-template-pane"
1475 }
1476 }
1477 "#]]
1478 .assert_eq(&kdl.0);
1479 }
1480
1481 #[test]
1482 fn can_serialize_swap_tiled_layout_with_a_single_pane() {
1483 let tiled_panes_layout = TiledPaneLayout {
1484 name: Some("my-only-swap-pane".to_owned()),
1485 ..Default::default()
1486 };
1487 let mut swap_tiled_layout = BTreeMap::new();
1488 swap_tiled_layout.insert(LayoutConstraint::NoConstraint, tiled_panes_layout);
1489 let mut default_layout = Layout::default();
1490 default_layout.swap_tiled_layouts = vec![(swap_tiled_layout, Some("my-swap".to_owned()))];
1491 let global_layout_manifest = GlobalLayoutManifest {
1492 default_layout: Box::new(default_layout),
1493 ..Default::default()
1494 };
1495 let kdl = serialize_session_layout(global_layout_manifest).unwrap();
1496 expect![[r#"
1497 layout {
1498 swap_tiled_layout name="my-swap" {
1499 tab {
1500 pane name="my-only-swap-pane"
1501 }
1502 }
1503 }
1504 "#]]
1505 .assert_eq(&kdl.0);
1506 }
1507
1508 #[test]
1509 fn can_serialize_tab_name() {
1510 let global_layout_manifest = GlobalLayoutManifest {
1511 tabs: vec![("my \"tab \\name".to_owned(), TabLayoutManifest::default())],
1512 ..Default::default()
1513 };
1514 let kdl = serialize_session_layout(global_layout_manifest).unwrap();
1515 assert_snapshot!(kdl.0);
1516 }
1517 #[test]
1518 fn can_serialize_tab_focus() {
1519 let tab_layout_manifest = TabLayoutManifest {
1520 is_focused: true,
1521 ..Default::default()
1522 };
1523 let global_layout_manifest = GlobalLayoutManifest {
1524 tabs: vec![("Tab #1".to_owned(), tab_layout_manifest)],
1525 ..Default::default()
1526 };
1527 let kdl = serialize_session_layout(global_layout_manifest).unwrap();
1528 assert_snapshot!(kdl.0);
1529 }
1530 #[test]
1531 fn can_serialize_tab_hide_floating_panes() {
1532 let tab_layout_manifest = TabLayoutManifest {
1533 hide_floating_panes: true,
1534 ..Default::default()
1535 };
1536 let global_layout_manifest = GlobalLayoutManifest {
1537 tabs: vec![("Tab #1".to_owned(), tab_layout_manifest)],
1538 ..Default::default()
1539 };
1540 let kdl = serialize_session_layout(global_layout_manifest).unwrap();
1541 assert_snapshot!(kdl.0);
1542 }
1543 #[test]
1544 fn can_serialize_tab_with_tiled_panes() {
1545 use crate::input::command::RunCommand;
1546 use crate::input::layout::RunPlugin;
1547 let mut plugin_configuration = BTreeMap::new();
1548 plugin_configuration.insert("key 1\"\\".to_owned(), "val 1\"\\".to_owned());
1549 plugin_configuration.insert("key 2\"\\".to_owned(), "val 2\"\\".to_owned());
1550 let tab_layout_manifest = TabLayoutManifest {
1551 tiled_panes: vec![
1552 PaneLayoutManifest {
1553 geom: PaneGeom {
1554 x: 0,
1555 y: 0,
1556 rows: Dimension::fixed(10),
1557 cols: Dimension::fixed(10),
1558 stacked: None,
1559 is_pinned: false,
1560 logical_position: None,
1561 },
1562 ..Default::default()
1563 },
1564 PaneLayoutManifest {
1565 run: Some(Run::Cwd(PathBuf::from("/tmp/\"my/cool cwd"))),
1566 geom: PaneGeom {
1567 x: 0,
1568 y: 10,
1569 rows: Dimension::fixed(10),
1570 cols: Dimension::fixed(10),
1571 stacked: None,
1572 is_pinned: false,
1573 logical_position: None,
1574 },
1575 ..Default::default()
1576 },
1577 PaneLayoutManifest {
1578 run: Some(Run::EditFile(
1579 PathBuf::from("/tmp/\"my/cool cwd/my-file"),
1580 None,
1581 None,
1582 )),
1583 geom: PaneGeom {
1584 x: 0,
1585 y: 20,
1586 rows: Dimension::fixed(10),
1587 cols: Dimension::fixed(10),
1588 stacked: None,
1589 is_pinned: false,
1590 logical_position: None,
1591 },
1592 ..Default::default()
1593 },
1594 PaneLayoutManifest {
1595 run: Some(Run::Command(RunCommand {
1596 command: PathBuf::from("/tmp/\"my/cool cwd/command.sh"),
1597 ..Default::default()
1598 })),
1599 geom: PaneGeom {
1600 x: 0,
1601 y: 30,
1602 rows: Dimension::fixed(10),
1603 cols: Dimension::fixed(10),
1604 stacked: None,
1605 is_pinned: false,
1606 logical_position: None,
1607 },
1608 ..Default::default()
1609 },
1610 PaneLayoutManifest {
1611 run: Some(Run::Command(RunCommand {
1612 command: PathBuf::from("/tmp/\"my/cool cwd/command.sh"),
1613 args: vec![
1614 "--arg1".to_owned(),
1615 "arg\"2".to_owned(),
1616 "arg > \\3".to_owned(),
1617 ],
1618 ..Default::default()
1619 })),
1620 geom: PaneGeom {
1621 x: 0,
1622 y: 40,
1623 rows: Dimension::fixed(10),
1624 cols: Dimension::fixed(10),
1625 stacked: None,
1626 is_pinned: false,
1627 logical_position: None,
1628 },
1629 ..Default::default()
1630 },
1631 PaneLayoutManifest {
1632 run: Some(Run::Plugin(RunPluginOrAlias::RunPlugin(
1633 RunPlugin::from_url("file:/tmp/\"my/cool cwd/plugin.wasm").unwrap(),
1634 ))),
1635 geom: PaneGeom {
1636 x: 0,
1637 y: 50,
1638 rows: Dimension::fixed(10),
1639 cols: Dimension::fixed(10),
1640 stacked: None,
1641 is_pinned: false,
1642 logical_position: None,
1643 },
1644 ..Default::default()
1645 },
1646 PaneLayoutManifest {
1647 run: Some(Run::Plugin(RunPluginOrAlias::RunPlugin(
1648 RunPlugin::from_url("file:/tmp/\"my/cool cwd/plugin.wasm")
1649 .unwrap()
1650 .with_configuration(plugin_configuration),
1651 ))),
1652 geom: PaneGeom {
1653 x: 0,
1654 y: 60,
1655 rows: Dimension::fixed(10),
1656 cols: Dimension::fixed(10),
1657 stacked: None,
1658 is_pinned: false,
1659 logical_position: None,
1660 },
1661 ..Default::default()
1662 },
1663 PaneLayoutManifest {
1664 is_borderless: true,
1665 geom: PaneGeom {
1666 x: 0,
1667 y: 70,
1668 rows: Dimension::fixed(10),
1669 cols: Dimension::fixed(10),
1670 stacked: None,
1671 is_pinned: false,
1672 logical_position: None,
1673 },
1674 ..Default::default()
1675 },
1676 PaneLayoutManifest {
1677 title: Some("my cool \\ \"pane_title\"".to_owned()),
1678 is_focused: true,
1679 pane_contents: Some("can has pane contents".to_owned()),
1680 geom: PaneGeom {
1681 x: 0,
1682 y: 80,
1683 rows: Dimension::fixed(10),
1684 cols: Dimension::fixed(10),
1685 stacked: None,
1686 is_pinned: false,
1687 logical_position: None,
1688 },
1689 ..Default::default()
1690 },
1691 ],
1692 ..Default::default()
1693 };
1694 let global_layout_manifest = GlobalLayoutManifest {
1695 tabs: vec![("Tab with \"tiled panes\"".to_owned(), tab_layout_manifest)],
1696 ..Default::default()
1697 };
1698 let kdl = serialize_session_layout(global_layout_manifest).unwrap();
1699 assert_snapshot!(kdl.0);
1700 }
1701 #[test]
1702 fn can_serialize_tab_with_floating_panes() {
1703 use crate::input::command::RunCommand;
1704 use crate::input::layout::RunPlugin;
1705 let mut plugin_configuration = BTreeMap::new();
1706 plugin_configuration.insert("key 1\"\\".to_owned(), "val 1\"\\".to_owned());
1707 plugin_configuration.insert("key 2\"\\".to_owned(), "val 2\"\\".to_owned());
1708 let tab_layout_manifest = TabLayoutManifest {
1709 floating_panes: vec![
1710 PaneLayoutManifest {
1711 geom: PaneGeom {
1712 x: 0,
1713 y: 0,
1714 rows: Dimension::fixed(10),
1715 cols: Dimension::fixed(10),
1716 stacked: None,
1717 is_pinned: false,
1718 logical_position: None,
1719 },
1720 ..Default::default()
1721 },
1722 PaneLayoutManifest {
1723 run: Some(Run::Cwd(PathBuf::from("/tmp/\"my/cool cwd"))),
1724 geom: PaneGeom {
1725 x: 0,
1726 y: 10,
1727 rows: Dimension::fixed(10),
1728 cols: Dimension::fixed(10),
1729 stacked: None,
1730 is_pinned: false,
1731 logical_position: None,
1732 },
1733 ..Default::default()
1734 },
1735 PaneLayoutManifest {
1736 run: Some(Run::EditFile(
1737 PathBuf::from("/tmp/\"my/cool cwd/my-file"),
1738 None,
1739 None,
1740 )),
1741 geom: PaneGeom {
1742 x: 0,
1743 y: 20,
1744 rows: Dimension::fixed(10),
1745 cols: Dimension::fixed(10),
1746 stacked: None,
1747 is_pinned: false,
1748 logical_position: None,
1749 },
1750 ..Default::default()
1751 },
1752 PaneLayoutManifest {
1753 run: Some(Run::Command(RunCommand {
1754 command: PathBuf::from("/tmp/\"my/cool cwd/command.sh"),
1755 ..Default::default()
1756 })),
1757 geom: PaneGeom {
1758 x: 0,
1759 y: 30,
1760 rows: Dimension::fixed(10),
1761 cols: Dimension::fixed(10),
1762 stacked: None,
1763 is_pinned: false,
1764 logical_position: None,
1765 },
1766 ..Default::default()
1767 },
1768 PaneLayoutManifest {
1769 run: Some(Run::Command(RunCommand {
1770 command: PathBuf::from("/tmp/\"my/cool cwd/command.sh"),
1771 args: vec![
1772 "--arg1".to_owned(),
1773 "arg\"2".to_owned(),
1774 "arg > \\3".to_owned(),
1775 ],
1776 ..Default::default()
1777 })),
1778 geom: PaneGeom {
1779 x: 0,
1780 y: 40,
1781 rows: Dimension::fixed(10),
1782 cols: Dimension::fixed(10),
1783 stacked: None,
1784 is_pinned: false,
1785 logical_position: None,
1786 },
1787 ..Default::default()
1788 },
1789 PaneLayoutManifest {
1790 run: Some(Run::Plugin(RunPluginOrAlias::RunPlugin(
1791 RunPlugin::from_url("file:/tmp/\"my/cool cwd/plugin.wasm").unwrap(),
1792 ))),
1793 geom: PaneGeom {
1794 x: 0,
1795 y: 50,
1796 rows: Dimension::fixed(10),
1797 cols: Dimension::fixed(10),
1798 stacked: None,
1799 is_pinned: false,
1800 logical_position: None,
1801 },
1802 ..Default::default()
1803 },
1804 PaneLayoutManifest {
1805 run: Some(Run::Plugin(RunPluginOrAlias::RunPlugin(
1806 RunPlugin::from_url("file:/tmp/\"my/cool cwd/plugin.wasm")
1807 .unwrap()
1808 .with_configuration(plugin_configuration),
1809 ))),
1810 geom: PaneGeom {
1811 x: 0,
1812 y: 60,
1813 rows: Dimension::fixed(10),
1814 cols: Dimension::fixed(10),
1815 stacked: None,
1816 is_pinned: false,
1817 logical_position: None,
1818 },
1819 ..Default::default()
1820 },
1821 PaneLayoutManifest {
1822 is_borderless: true,
1825 geom: PaneGeom {
1826 x: 0,
1827 y: 70,
1828 rows: Dimension::fixed(10),
1829 cols: Dimension::fixed(10),
1830 stacked: None,
1831 is_pinned: false,
1832 logical_position: None,
1833 },
1834 ..Default::default()
1835 },
1836 PaneLayoutManifest {
1837 title: Some("my cool \\ \"pane_title\"".to_owned()),
1838 is_focused: true,
1839 pane_contents: Some("can has pane contents".to_owned()),
1840 geom: PaneGeom {
1841 x: 0,
1842 y: 80,
1843 rows: Dimension::fixed(10),
1844 cols: Dimension::fixed(10),
1845 stacked: None,
1846 is_pinned: false,
1847 logical_position: None,
1848 },
1849 ..Default::default()
1850 },
1851 ],
1852 ..Default::default()
1853 };
1854 let global_layout_manifest = GlobalLayoutManifest {
1855 tabs: vec![(
1856 "Tab with \"floating panes\"".to_owned(),
1857 tab_layout_manifest,
1858 )],
1859 ..Default::default()
1860 };
1861 let kdl = serialize_session_layout(global_layout_manifest).unwrap();
1862 assert_snapshot!(kdl.0);
1863 }
1864 #[test]
1865 fn can_serialize_tab_with_stacked_panes() {
1866 let tab_layout_manifest = TabLayoutManifest {
1867 tiled_panes: vec![
1868 PaneLayoutManifest {
1869 geom: PaneGeom {
1870 x: 0,
1871 y: 0,
1872 rows: Dimension::fixed(1),
1873 cols: Dimension::fixed(10),
1874 stacked: Some(0),
1875 is_pinned: false,
1876 logical_position: None,
1877 },
1878 ..Default::default()
1879 },
1880 PaneLayoutManifest {
1881 geom: PaneGeom {
1882 x: 0,
1883 y: 1,
1884 rows: Dimension::fixed(10),
1885 cols: Dimension::fixed(10),
1886 stacked: Some(0),
1887 is_pinned: false,
1888 logical_position: None,
1889 },
1890 ..Default::default()
1891 },
1892 PaneLayoutManifest {
1893 geom: PaneGeom {
1894 x: 0,
1895 y: 11,
1896 rows: Dimension::fixed(1),
1897 cols: Dimension::fixed(10),
1898 stacked: Some(0),
1899 is_pinned: false,
1900 logical_position: None,
1901 },
1902 ..Default::default()
1903 },
1904 ],
1905 ..Default::default()
1906 };
1907 let global_layout_manifest = GlobalLayoutManifest {
1908 tabs: vec![("Tab with \"stacked panes\"".to_owned(), tab_layout_manifest)],
1909 ..Default::default()
1910 };
1911 let kdl = serialize_session_layout(global_layout_manifest).unwrap();
1912 assert_snapshot!(kdl.0);
1913 }
1914 #[test]
1915 fn can_serialize_tab_with_multiple_stacked_panes_in_the_same_node() {
1916 let tab_layout_manifest = TabLayoutManifest {
1917 tiled_panes: vec![
1918 PaneLayoutManifest {
1919 geom: PaneGeom {
1920 x: 0,
1921 y: 0,
1922 rows: Dimension::fixed(1),
1923 cols: Dimension::fixed(10),
1924 stacked: Some(0),
1925 is_pinned: false,
1926 logical_position: None,
1927 },
1928 ..Default::default()
1929 },
1930 PaneLayoutManifest {
1931 geom: PaneGeom {
1932 x: 0,
1933 y: 1,
1934 rows: Dimension::fixed(10),
1935 cols: Dimension::fixed(10),
1936 stacked: Some(0),
1937 is_pinned: false,
1938 logical_position: None,
1939 },
1940 ..Default::default()
1941 },
1942 PaneLayoutManifest {
1943 geom: PaneGeom {
1944 x: 0,
1945 y: 11,
1946 rows: Dimension::fixed(1),
1947 cols: Dimension::fixed(10),
1948 stacked: Some(0),
1949 is_pinned: false,
1950 logical_position: None,
1951 },
1952 ..Default::default()
1953 },
1954 PaneLayoutManifest {
1955 geom: PaneGeom {
1956 x: 0,
1957 y: 12,
1958 rows: Dimension::fixed(10),
1959 cols: Dimension::fixed(10),
1960 stacked: None,
1961 is_pinned: false,
1962 logical_position: None,
1963 },
1964 ..Default::default()
1965 },
1966 PaneLayoutManifest {
1967 geom: PaneGeom {
1968 x: 0,
1969 y: 22,
1970 rows: Dimension::fixed(1),
1971 cols: Dimension::fixed(10),
1972 stacked: Some(1),
1973 is_pinned: false,
1974 logical_position: None,
1975 },
1976 ..Default::default()
1977 },
1978 PaneLayoutManifest {
1979 geom: PaneGeom {
1980 x: 0,
1981 y: 23,
1982 rows: Dimension::fixed(10),
1983 cols: Dimension::fixed(10),
1984 stacked: Some(1),
1985 is_pinned: false,
1986 logical_position: None,
1987 },
1988 ..Default::default()
1989 },
1990 PaneLayoutManifest {
1991 geom: PaneGeom {
1992 x: 0,
1993 y: 33,
1994 rows: Dimension::fixed(1),
1995 cols: Dimension::fixed(10),
1996 stacked: Some(1),
1997 is_pinned: false,
1998 logical_position: None,
1999 },
2000 ..Default::default()
2001 },
2002 ],
2003 ..Default::default()
2004 };
2005 let global_layout_manifest = GlobalLayoutManifest {
2006 tabs: vec![("Tab with \"stacked panes\"".to_owned(), tab_layout_manifest)],
2007 ..Default::default()
2008 };
2009 let kdl = serialize_session_layout(global_layout_manifest).unwrap();
2010 assert_snapshot!(kdl.0);
2011 }
2012 #[test]
2013 fn can_serialize_tab_with_multiple_stacks_next_to_eachother() {
2014 let tab_layout_manifest = TabLayoutManifest {
2015 tiled_panes: vec![
2016 PaneLayoutManifest {
2017 geom: PaneGeom {
2018 x: 0,
2019 y: 0,
2020 rows: Dimension::fixed(1),
2021 cols: Dimension::fixed(10),
2022 stacked: Some(0),
2023 is_pinned: false,
2024 logical_position: None,
2025 },
2026 ..Default::default()
2027 },
2028 PaneLayoutManifest {
2029 geom: PaneGeom {
2030 x: 0,
2031 y: 1,
2032 rows: Dimension::fixed(10),
2033 cols: Dimension::fixed(10),
2034 stacked: Some(0),
2035 is_pinned: false,
2036 logical_position: None,
2037 },
2038 ..Default::default()
2039 },
2040 PaneLayoutManifest {
2041 geom: PaneGeom {
2042 x: 0,
2043 y: 11,
2044 rows: Dimension::fixed(1),
2045 cols: Dimension::fixed(10),
2046 stacked: Some(0),
2047 is_pinned: false,
2048 logical_position: None,
2049 },
2050 ..Default::default()
2051 },
2052 PaneLayoutManifest {
2053 geom: PaneGeom {
2054 x: 0,
2055 y: 12,
2056 rows: Dimension::fixed(10),
2057 cols: Dimension::fixed(10),
2058 stacked: None,
2059 is_pinned: false,
2060 logical_position: None,
2061 },
2062 ..Default::default()
2063 },
2064 PaneLayoutManifest {
2065 geom: PaneGeom {
2066 x: 0,
2067 y: 22,
2068 rows: Dimension::fixed(1),
2069 cols: Dimension::fixed(10),
2070 stacked: Some(1),
2071 is_pinned: false,
2072 logical_position: None,
2073 },
2074 ..Default::default()
2075 },
2076 PaneLayoutManifest {
2077 geom: PaneGeom {
2078 x: 0,
2079 y: 23,
2080 rows: Dimension::fixed(10),
2081 cols: Dimension::fixed(10),
2082 stacked: Some(1),
2083 is_pinned: false,
2084 logical_position: None,
2085 },
2086 ..Default::default()
2087 },
2088 PaneLayoutManifest {
2089 geom: PaneGeom {
2090 x: 0,
2091 y: 33,
2092 rows: Dimension::fixed(1),
2093 cols: Dimension::fixed(10),
2094 stacked: Some(1),
2095 is_pinned: false,
2096 logical_position: None,
2097 },
2098 ..Default::default()
2099 },
2100 PaneLayoutManifest {
2101 geom: PaneGeom {
2102 x: 10,
2103 y: 0,
2104 rows: Dimension::fixed(1),
2105 cols: Dimension::fixed(10),
2106 stacked: Some(2),
2107 is_pinned: false,
2108 logical_position: None,
2109 },
2110 ..Default::default()
2111 },
2112 PaneLayoutManifest {
2113 geom: PaneGeom {
2114 x: 10,
2115 y: 1,
2116 rows: Dimension::fixed(10),
2117 cols: Dimension::fixed(10),
2118 stacked: Some(2),
2119 is_pinned: false,
2120 logical_position: None,
2121 },
2122 ..Default::default()
2123 },
2124 PaneLayoutManifest {
2125 geom: PaneGeom {
2126 x: 10,
2127 y: 11,
2128 rows: Dimension::fixed(1),
2129 cols: Dimension::fixed(10),
2130 stacked: Some(2),
2131 is_pinned: false,
2132 logical_position: None,
2133 },
2134 ..Default::default()
2135 },
2136 PaneLayoutManifest {
2137 geom: PaneGeom {
2138 x: 10,
2139 y: 12,
2140 rows: Dimension::fixed(10),
2141 cols: Dimension::fixed(10),
2142 stacked: None,
2143 is_pinned: false,
2144 logical_position: None,
2145 },
2146 ..Default::default()
2147 },
2148 PaneLayoutManifest {
2149 geom: PaneGeom {
2150 x: 10,
2151 y: 22,
2152 rows: Dimension::fixed(1),
2153 cols: Dimension::fixed(10),
2154 stacked: Some(3),
2155 is_pinned: false,
2156 logical_position: None,
2157 },
2158 ..Default::default()
2159 },
2160 PaneLayoutManifest {
2161 geom: PaneGeom {
2162 x: 10,
2163 y: 23,
2164 rows: Dimension::fixed(10),
2165 cols: Dimension::fixed(10),
2166 stacked: Some(3),
2167 is_pinned: false,
2168 logical_position: None,
2169 },
2170 ..Default::default()
2171 },
2172 PaneLayoutManifest {
2173 geom: PaneGeom {
2174 x: 10,
2175 y: 33,
2176 rows: Dimension::fixed(1),
2177 cols: Dimension::fixed(10),
2178 stacked: Some(3),
2179 is_pinned: false,
2180 logical_position: None,
2181 },
2182 ..Default::default()
2183 },
2184 ],
2185 ..Default::default()
2186 };
2187 let global_layout_manifest = GlobalLayoutManifest {
2188 tabs: vec![("Tab with \"stacked panes\"".to_owned(), tab_layout_manifest)],
2189 ..Default::default()
2190 };
2191 let kdl = serialize_session_layout(global_layout_manifest).unwrap();
2192 assert_snapshot!(kdl.0);
2193 }
2194 #[test]
2195 fn can_serialize_multiple_tabs() {
2196 let tab_1_layout_manifest = TabLayoutManifest {
2197 tiled_panes: vec![PaneLayoutManifest {
2198 geom: PaneGeom {
2199 x: 0,
2200 y: 0,
2201 rows: Dimension::percent(100.0),
2202 cols: Dimension::percent(100.0),
2203 stacked: None,
2204 is_pinned: false,
2205 logical_position: None,
2206 },
2207 ..Default::default()
2208 }],
2209 ..Default::default()
2210 };
2211 let tab_2_layout_manifest = TabLayoutManifest {
2212 tiled_panes: vec![
2213 PaneLayoutManifest {
2214 geom: PaneGeom {
2215 x: 0,
2216 y: 0,
2217 rows: Dimension::fixed(10),
2218 cols: Dimension::fixed(10),
2219 stacked: None,
2220 is_pinned: false,
2221 logical_position: None,
2222 },
2223 ..Default::default()
2224 },
2225 PaneLayoutManifest {
2226 geom: PaneGeom {
2227 x: 10,
2228 y: 0,
2229 rows: Dimension::fixed(10),
2230 cols: Dimension::fixed(10),
2231 stacked: None,
2232 is_pinned: false,
2233 logical_position: None,
2234 },
2235 ..Default::default()
2236 },
2237 ],
2238 ..Default::default()
2239 };
2240 let global_layout_manifest = GlobalLayoutManifest {
2241 tabs: vec![
2242 ("First tab".to_owned(), tab_1_layout_manifest),
2243 ("Second tab".to_owned(), tab_2_layout_manifest),
2244 ],
2245 ..Default::default()
2246 };
2247 let kdl = serialize_session_layout(global_layout_manifest).unwrap();
2248 assert_snapshot!(kdl.0);
2249 }
2250 #[test]
2251 fn can_serialize_new_tab_template() {
2252 let tiled_panes_layout = TiledPaneLayout {
2253 children: vec![TiledPaneLayout::default(), TiledPaneLayout::default()],
2254 ..Default::default()
2255 };
2256
2257 let floating_panes_layout = vec![
2258 FloatingPaneLayout::default(),
2259 FloatingPaneLayout::default(),
2260 FloatingPaneLayout::default(),
2261 ];
2262 let mut default_layout = Layout::default();
2263 default_layout.template = Some((tiled_panes_layout, floating_panes_layout));
2264 let default_layout = Box::new(default_layout);
2265 let global_layout_manifest = GlobalLayoutManifest {
2266 default_layout,
2267 ..Default::default()
2268 };
2269 let kdl = serialize_session_layout(global_layout_manifest).unwrap();
2270 assert_snapshot!(kdl.0);
2271 }
2272 #[test]
2273 fn can_serialize_swap_tiled_panes() {
2274 let tiled_panes_layout = TiledPaneLayout {
2275 children: vec![TiledPaneLayout::default(), TiledPaneLayout::default()],
2276 ..Default::default()
2277 };
2278 let mut default_layout = Layout::default();
2279 let mut swap_tiled_layout_1 = BTreeMap::new();
2280 let mut swap_tiled_layout_2 = BTreeMap::new();
2281 swap_tiled_layout_1.insert(LayoutConstraint::MaxPanes(1), tiled_panes_layout.clone());
2282 swap_tiled_layout_1.insert(LayoutConstraint::MinPanes(1), tiled_panes_layout.clone());
2283 swap_tiled_layout_1.insert(LayoutConstraint::ExactPanes(1), tiled_panes_layout.clone());
2284 swap_tiled_layout_1.insert(LayoutConstraint::NoConstraint, tiled_panes_layout.clone());
2285 swap_tiled_layout_2.insert(LayoutConstraint::MaxPanes(2), tiled_panes_layout.clone());
2286 swap_tiled_layout_2.insert(LayoutConstraint::MinPanes(2), tiled_panes_layout.clone());
2287 swap_tiled_layout_2.insert(LayoutConstraint::ExactPanes(2), tiled_panes_layout.clone());
2288 swap_tiled_layout_2.insert(LayoutConstraint::NoConstraint, tiled_panes_layout.clone());
2289
2290 let swap_tiled_layouts = vec![
2291 (swap_tiled_layout_1, None),
2292 (swap_tiled_layout_2, Some("swap_tiled_layout_2".to_owned())),
2293 ];
2294 default_layout.swap_tiled_layouts = swap_tiled_layouts;
2295 let default_layout = Box::new(default_layout);
2296 let global_layout_manifest = GlobalLayoutManifest {
2297 default_layout,
2298 ..Default::default()
2299 };
2300 let kdl = serialize_session_layout(global_layout_manifest).unwrap();
2301 assert_snapshot!(kdl.0);
2302 }
2303 #[test]
2304 fn can_serialize_swap_floating_panes() {
2305 let floating_panes_layout = vec![
2306 FloatingPaneLayout::default(),
2307 FloatingPaneLayout::default(),
2308 FloatingPaneLayout::default(),
2309 ];
2310 let mut default_layout = Layout::default();
2311 let mut swap_floating_layout_1 = BTreeMap::new();
2312 let mut swap_floating_layout_2 = BTreeMap::new();
2313 swap_floating_layout_1.insert(LayoutConstraint::MaxPanes(1), floating_panes_layout.clone());
2314 swap_floating_layout_1.insert(LayoutConstraint::MinPanes(1), floating_panes_layout.clone());
2315 swap_floating_layout_1.insert(
2316 LayoutConstraint::ExactPanes(1),
2317 floating_panes_layout.clone(),
2318 );
2319 swap_floating_layout_1.insert(
2320 LayoutConstraint::NoConstraint,
2321 floating_panes_layout.clone(),
2322 );
2323 swap_floating_layout_2.insert(LayoutConstraint::MaxPanes(2), floating_panes_layout.clone());
2324 swap_floating_layout_2.insert(LayoutConstraint::MinPanes(2), floating_panes_layout.clone());
2325 swap_floating_layout_2.insert(
2326 LayoutConstraint::ExactPanes(2),
2327 floating_panes_layout.clone(),
2328 );
2329 swap_floating_layout_2.insert(
2330 LayoutConstraint::NoConstraint,
2331 floating_panes_layout.clone(),
2332 );
2333
2334 let swap_floating_layouts = vec![
2335 (swap_floating_layout_1, None),
2336 (
2337 swap_floating_layout_2,
2338 Some("swap_floating_layout_2".to_owned()),
2339 ),
2340 ];
2341 default_layout.swap_floating_layouts = swap_floating_layouts;
2342 let default_layout = Box::new(default_layout);
2343 let global_layout_manifest = GlobalLayoutManifest {
2344 default_layout,
2345 ..Default::default()
2346 };
2347 let kdl = serialize_session_layout(global_layout_manifest).unwrap();
2348 assert_snapshot!(kdl.0);
2349 }
2350
2351 fn parse_panegeom_from_json(data_str: &str) -> PaneGeom {
2353 let data: HashMap<String, Value> = serde_json::from_str(data_str).unwrap();
2359 PaneGeom {
2360 x: data["x"].to_string().parse().unwrap(),
2361 y: data["y"].to_string().parse().unwrap(),
2362 rows: get_dim(&data["rows"]),
2363 cols: get_dim(&data["cols"]),
2364 stacked: None,
2365 is_pinned: false,
2366 logical_position: None,
2367 }
2368 }
2369
2370 fn get_dim(dim_hm: &Value) -> Dimension {
2371 let constr_str = dim_hm["constraint"].to_string();
2372 let dim = if constr_str.contains("Fixed") {
2373 let value = &constr_str[7..constr_str.len() - 2];
2374 Dimension::fixed(value.parse().unwrap())
2375 } else if constr_str.contains("Percent") {
2376 let value = &constr_str[9..constr_str.len() - 2];
2377 let mut dim = Dimension::percent(value.parse().unwrap());
2378 dim.set_inner(dim_hm["inner"].to_string().parse().unwrap());
2379 dim
2380 } else {
2381 panic!("Constraint is nor a percent nor fixed");
2382 };
2383 dim
2384 }
2385}