mirror of
https://github.com/zellij-org/zellij.git
synced 2026-10-11 22:50:07 +00:00
fix(resurrection): stop saving layouts that fail to reload, and attach to live sessions regardless (#5690)
* fix(resurrection): stop saving layouts that fail to reload, and attach to live sessions regardless * add pr
This commit is contained in:
@@ -26,6 +26,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||
* feat: right-click menu, interactive configuration, popups, notifications (https://github.com/zellij-org/zellij/pull/5685)
|
||||
* fix: preserve order of command panes when changing swap layouts (https://github.com/zellij-org/zellij/pull/5687)
|
||||
* feat: let a plugin give its layout space back while it has nothing to draw, so a bar plugin does not hold an empty row (https://github.com/zellij-org/zellij/pull/5590)
|
||||
* fix: occasional resurrection layout serialization corruption (https://github.com/zellij-org/zellij/pull/5690)
|
||||
|
||||
## [0.45.1] - 2026-08-28
|
||||
* fix: nested-session detection over SSH (https://github.com/zellij-org/zellij/pull/5522)
|
||||
|
||||
+10
-10
@@ -806,16 +806,16 @@ pub(crate) fn start_client(opts: CliArgs) {
|
||||
.as_ref()
|
||||
.and_then(|s| session_exists(&s).ok())
|
||||
.unwrap_or(false);
|
||||
let resurrection_layout =
|
||||
session_name
|
||||
.as_ref()
|
||||
.and_then(|s| match resurrection_layout(&s) {
|
||||
Ok(layout) => layout,
|
||||
Err(e) => {
|
||||
eprintln!("{}", e);
|
||||
process::exit(2);
|
||||
},
|
||||
});
|
||||
let resurrection_layout = session_name
|
||||
.as_ref()
|
||||
.filter(|_| !session_exists)
|
||||
.and_then(|s| match resurrection_layout(&s) {
|
||||
Ok(layout) => layout,
|
||||
Err(e) => {
|
||||
eprintln!("{}", e);
|
||||
process::exit(2);
|
||||
},
|
||||
});
|
||||
if (create || should_create_detached)
|
||||
&& !session_exists
|
||||
&& resurrection_layout.is_none()
|
||||
|
||||
@@ -1109,6 +1109,9 @@ impl<'a> StackedPanes<'a> {
|
||||
flexible_pane_position_and_size
|
||||
.rows
|
||||
.set_inner(flexible_pane_position_and_size.rows.as_usize() + 1);
|
||||
if all_stacked_pane_positions.len() == 2 {
|
||||
flexible_pane_position_and_size.stacked = None;
|
||||
}
|
||||
flexible_pane.set_geom(flexible_pane_position_and_size);
|
||||
for (i, (pid, _position)) in all_stacked_pane_positions.iter().enumerate() {
|
||||
if i > position_of_flexible_pane && i < position_of_current_pane {
|
||||
@@ -1142,6 +1145,9 @@ impl<'a> StackedPanes<'a> {
|
||||
flexible_pane_position_and_size
|
||||
.rows
|
||||
.set_inner(flexible_pane_position_and_size.rows.as_usize() + 1);
|
||||
if all_stacked_pane_positions.len() == 2 {
|
||||
flexible_pane_position_and_size.stacked = None;
|
||||
}
|
||||
flexible_pane.set_geom(flexible_pane_position_and_size);
|
||||
for (i, (pid, _position)) in all_stacked_pane_positions.iter().enumerate() {
|
||||
if i > position_of_current_pane && i <= position_of_flexible_pane {
|
||||
|
||||
@@ -67,6 +67,25 @@ macro_rules! mock_stacked_pane {
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! mock_stacked_pane_with_pid {
|
||||
($pane_id:expr, $dimension:expr, $inner:expr, $y:expr, $mock_panes:expr) => {
|
||||
let mut mock_pane_rows = $dimension;
|
||||
mock_pane_rows.set_inner($inner);
|
||||
let mut mock_pane: Box<dyn Pane> = Box::new(MockPane::with_id(
|
||||
$pane_id,
|
||||
PaneGeom {
|
||||
x: 0,
|
||||
y: $y,
|
||||
rows: mock_pane_rows,
|
||||
cols: Dimension::percent(100.0),
|
||||
stacked: Some(0),
|
||||
..Default::default()
|
||||
},
|
||||
));
|
||||
$mock_panes.insert($pane_id, &mut mock_pane);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! mock_stacked_pane_with_id {
|
||||
($pane_id:expr, $dimension:expr, $inner:expr, $x:expr, $y:expr, $logical_position:expr, $mock_panes:expr, $stack_id:expr) => {
|
||||
let mut mock_pane_rows = $dimension;
|
||||
@@ -1037,11 +1056,21 @@ fn break_next_to_last_pane_out_of_stack() {
|
||||
|
||||
struct MockPane {
|
||||
pane_geom: PaneGeom,
|
||||
pane_id: Option<PaneId>,
|
||||
}
|
||||
|
||||
impl MockPane {
|
||||
pub fn new(pane_geom: PaneGeom) -> Self {
|
||||
MockPane { pane_geom }
|
||||
MockPane {
|
||||
pane_geom,
|
||||
pane_id: None,
|
||||
}
|
||||
}
|
||||
pub fn with_id(pane_id: PaneId, pane_geom: PaneGeom) -> Self {
|
||||
MockPane {
|
||||
pane_geom,
|
||||
pane_id: Some(pane_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1150,7 +1179,7 @@ impl Pane for MockPane {
|
||||
unimplemented!()
|
||||
}
|
||||
fn pid(&self) -> PaneId {
|
||||
unimplemented!()
|
||||
self.pane_id.unwrap_or_else(|| unimplemented!())
|
||||
}
|
||||
fn reduce_height(&mut self, _percent: f64) {
|
||||
unimplemented!()
|
||||
@@ -1252,3 +1281,77 @@ impl Pane for MockPane {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closing_one_liner_above_flexible_pane_in_two_pane_stack_dissolves_stack() {
|
||||
let mut mock_panes: HashMap<PaneId, &mut Box<dyn Pane>> = HashMap::new();
|
||||
mock_stacked_pane_with_pid!(PaneId::Terminal(1), Dimension::fixed(1), 1, 0, mock_panes);
|
||||
mock_stacked_pane_with_pid!(
|
||||
PaneId::Terminal(2),
|
||||
Dimension::percent(100.0),
|
||||
49,
|
||||
1,
|
||||
mock_panes
|
||||
);
|
||||
let mock_panes = Rc::new(RefCell::new(mock_panes));
|
||||
StackedPanes::new(mock_panes.clone())
|
||||
.fill_space_over_pane_in_stack(&PaneId::Terminal(1))
|
||||
.unwrap();
|
||||
let remaining_geom = mock_panes
|
||||
.borrow()
|
||||
.get(&PaneId::Terminal(2))
|
||||
.unwrap()
|
||||
.current_geom();
|
||||
assert_eq!(remaining_geom.stacked, None);
|
||||
assert_eq!(remaining_geom.y, 0);
|
||||
assert_eq!(remaining_geom.rows.as_usize(), 50);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closing_one_liner_below_flexible_pane_in_two_pane_stack_dissolves_stack() {
|
||||
let mut mock_panes: HashMap<PaneId, &mut Box<dyn Pane>> = HashMap::new();
|
||||
mock_stacked_pane_with_pid!(
|
||||
PaneId::Terminal(1),
|
||||
Dimension::percent(100.0),
|
||||
49,
|
||||
0,
|
||||
mock_panes
|
||||
);
|
||||
mock_stacked_pane_with_pid!(PaneId::Terminal(2), Dimension::fixed(1), 1, 49, mock_panes);
|
||||
let mock_panes = Rc::new(RefCell::new(mock_panes));
|
||||
StackedPanes::new(mock_panes.clone())
|
||||
.fill_space_over_pane_in_stack(&PaneId::Terminal(2))
|
||||
.unwrap();
|
||||
let remaining_geom = mock_panes
|
||||
.borrow()
|
||||
.get(&PaneId::Terminal(1))
|
||||
.unwrap()
|
||||
.current_geom();
|
||||
assert_eq!(remaining_geom.stacked, None);
|
||||
assert_eq!(remaining_geom.y, 0);
|
||||
assert_eq!(remaining_geom.rows.as_usize(), 50);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closing_one_liner_in_three_pane_stack_keeps_stack() {
|
||||
let mut mock_panes: HashMap<PaneId, &mut Box<dyn Pane>> = HashMap::new();
|
||||
mock_stacked_pane_with_pid!(PaneId::Terminal(1), Dimension::fixed(1), 1, 0, mock_panes);
|
||||
mock_stacked_pane_with_pid!(
|
||||
PaneId::Terminal(2),
|
||||
Dimension::percent(100.0),
|
||||
48,
|
||||
1,
|
||||
mock_panes
|
||||
);
|
||||
mock_stacked_pane_with_pid!(PaneId::Terminal(3), Dimension::fixed(1), 1, 49, mock_panes);
|
||||
let mock_panes = Rc::new(RefCell::new(mock_panes));
|
||||
StackedPanes::new(mock_panes.clone())
|
||||
.fill_space_over_pane_in_stack(&PaneId::Terminal(1))
|
||||
.unwrap();
|
||||
let flexible_geom = mock_panes
|
||||
.borrow()
|
||||
.get(&PaneId::Terminal(2))
|
||||
.unwrap()
|
||||
.current_geom();
|
||||
assert_eq!(flexible_geom.stacked, Some(0));
|
||||
}
|
||||
|
||||
@@ -129,7 +129,29 @@ fn serialize_tab(
|
||||
}
|
||||
}
|
||||
|
||||
fn flatten_nested_stacks(mut layout: TiledPaneLayout) -> TiledPaneLayout {
|
||||
layout.children = layout
|
||||
.children
|
||||
.into_iter()
|
||||
.map(flatten_nested_stacks)
|
||||
.collect();
|
||||
if layout.children_are_stacked
|
||||
&& layout.children.len() == 1
|
||||
&& layout.external_children_index.is_none()
|
||||
&& layout.children[0].children_are_stacked
|
||||
{
|
||||
let only_child = layout.children.remove(0);
|
||||
layout.children = only_child.children;
|
||||
layout.external_children_index = only_child.external_children_index;
|
||||
}
|
||||
if layout.children_are_stacked && layout.children.iter().any(|c| !c.children.is_empty()) {
|
||||
layout.children_are_stacked = false;
|
||||
}
|
||||
layout
|
||||
}
|
||||
|
||||
fn tiled_panes_to_serialize(root: TiledPaneLayout) -> Vec<TiledPaneLayout> {
|
||||
let root = flatten_nested_stacks(root);
|
||||
let root_is_leaf = root.children.is_empty() && root.external_children_index.is_none();
|
||||
if root_is_leaf {
|
||||
if root == TiledPaneLayout::default() {
|
||||
@@ -153,8 +175,8 @@ fn serialize_tiled_and_floating_panes(
|
||||
serialized_tab_children: &mut KdlDocument,
|
||||
) {
|
||||
for tiled_pane_layout in tiled_panes {
|
||||
let ignore_size = false;
|
||||
let tiled_pane_node = serialize_tiled_pane(tiled_pane_layout, ignore_size, pane_contents);
|
||||
let is_in_stack = false;
|
||||
let tiled_pane_node = serialize_tiled_pane(tiled_pane_layout, is_in_stack, pane_contents);
|
||||
serialized_tab_children.nodes_mut().push(tiled_pane_node);
|
||||
}
|
||||
if !floating_panes_layout.is_empty() {
|
||||
@@ -173,7 +195,7 @@ fn serialize_tiled_and_floating_panes(
|
||||
|
||||
fn serialize_tiled_pane(
|
||||
layout: &TiledPaneLayout,
|
||||
ignore_size: bool,
|
||||
is_in_stack: bool,
|
||||
pane_contents: &mut BTreeMap<String, String>,
|
||||
) -> KdlNode {
|
||||
let (command, args) = extract_command_and_args(&layout.run);
|
||||
@@ -195,7 +217,7 @@ fn serialize_tiled_pane(
|
||||
&mut tiled_pane_node,
|
||||
);
|
||||
|
||||
serialize_tiled_layout_attributes(&layout, ignore_size, &mut tiled_pane_node);
|
||||
serialize_tiled_layout_attributes(&layout, is_in_stack, &mut tiled_pane_node);
|
||||
if let Some(ref fg) = layout.default_fg {
|
||||
tiled_pane_node
|
||||
.entries_mut()
|
||||
@@ -227,8 +249,8 @@ fn serialize_tiled_pane(
|
||||
.nodes_mut()
|
||||
.push(KdlNode::new("children"));
|
||||
} else {
|
||||
let ignore_size = layout.children_are_stacked;
|
||||
let child_pane_node = serialize_tiled_pane(&pane, ignore_size, pane_contents);
|
||||
let is_in_stack = layout.children_are_stacked;
|
||||
let child_pane_node = serialize_tiled_pane(&pane, is_in_stack, pane_contents);
|
||||
tiled_pane_node_children.nodes_mut().push(child_pane_node);
|
||||
}
|
||||
}
|
||||
@@ -298,10 +320,10 @@ fn serialize_pane_title_and_attributes(
|
||||
match (&command, &edit) {
|
||||
(Some(command), _) => kdl_node
|
||||
.entries_mut()
|
||||
.push(KdlEntry::new_prop("command", command.to_owned())),
|
||||
.push(KdlEntry::new_prop("command", escape_dollar_signs(command))),
|
||||
(None, Some(edit)) => kdl_node
|
||||
.entries_mut()
|
||||
.push(KdlEntry::new_prop("edit", edit.to_owned())),
|
||||
.push(KdlEntry::new_prop("edit", escape_dollar_signs(edit))),
|
||||
_ => {},
|
||||
};
|
||||
if let Some(name) = name {
|
||||
@@ -314,7 +336,7 @@ fn serialize_pane_title_and_attributes(
|
||||
if !path.is_empty() && !has_children {
|
||||
kdl_node
|
||||
.entries_mut()
|
||||
.push(KdlEntry::new_prop("cwd", path.to_owned()));
|
||||
.push(KdlEntry::new_prop("cwd", escape_dollar_signs(&path)));
|
||||
}
|
||||
}
|
||||
if focus.unwrap_or(false) {
|
||||
@@ -373,10 +395,10 @@ fn serialize_plugin(
|
||||
|
||||
fn serialize_tiled_layout_attributes(
|
||||
layout: &TiledPaneLayout,
|
||||
ignore_size: bool,
|
||||
is_in_stack: bool,
|
||||
kdl_node: &mut KdlNode,
|
||||
) {
|
||||
if !ignore_size {
|
||||
if !is_in_stack {
|
||||
match layout.split_size {
|
||||
Some(SplitSize::Fixed(size)) => kdl_node
|
||||
.entries_mut()
|
||||
@@ -404,7 +426,7 @@ fn serialize_tiled_layout_attributes(
|
||||
.entries_mut()
|
||||
.push(KdlEntry::new_prop("stacked", KdlValue::Bool(true)));
|
||||
}
|
||||
if layout.is_expanded_in_stack {
|
||||
if layout.is_expanded_in_stack && is_in_stack {
|
||||
kdl_node
|
||||
.entries_mut()
|
||||
.push(KdlEntry::new_prop("expanded", KdlValue::Bool(true)));
|
||||
@@ -537,11 +559,15 @@ fn serialize_start_suspended(command: &Option<String>, pane_node_children: &mut
|
||||
fn serialize_global_cwd(global_cwd: &Option<PathBuf>) -> Option<KdlNode> {
|
||||
global_cwd.as_ref().map(|cwd| {
|
||||
let mut node = KdlNode::new("cwd");
|
||||
node.push(cwd.display().to_string());
|
||||
node.push(escape_dollar_signs(&cwd.display().to_string()));
|
||||
node
|
||||
})
|
||||
}
|
||||
|
||||
fn escape_dollar_signs(value: &str) -> String {
|
||||
value.replace('$', "$$")
|
||||
}
|
||||
|
||||
fn serialize_new_tab_template(
|
||||
new_tab_template: Option<(TiledPaneLayout, Vec<FloatingPaneLayout>)>,
|
||||
pane_contents: &mut BTreeMap<String, String>,
|
||||
@@ -913,8 +939,8 @@ fn get_floating_panes_layout_from_panegeoms(
|
||||
}
|
||||
FloatingPaneLayout {
|
||||
name: m.title.clone(),
|
||||
height: Some(m.geom.rows.into()),
|
||||
width: Some(m.geom.cols.into()),
|
||||
height: Some(at_least_one(m.geom.rows.into())),
|
||||
width: Some(at_least_one(m.geom.cols.into())),
|
||||
x: Some(PercentOrFixed::Fixed(m.geom.x)),
|
||||
y: Some(PercentOrFixed::Fixed(m.geom.y)),
|
||||
pinned: Some(m.geom.is_pinned),
|
||||
@@ -932,6 +958,13 @@ fn get_floating_panes_layout_from_panegeoms(
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn at_least_one(size: PercentOrFixed) -> PercentOrFixed {
|
||||
match size {
|
||||
PercentOrFixed::Percent(percent) => PercentOrFixed::Percent(percent.max(1)),
|
||||
PercentOrFixed::Fixed(fixed) => PercentOrFixed::Fixed(fixed.max(1)),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_x_lims(geoms: &Vec<PaneLayoutManifest>) -> Option<(usize, usize)> {
|
||||
match (
|
||||
geoms.iter().map(|g| g.geom.x).min(),
|
||||
@@ -1165,7 +1198,9 @@ fn get_split_sizes(constraints: &Vec<Constraint>) -> Vec<Option<SplitSize>> {
|
||||
if size == &max_percent {
|
||||
None
|
||||
} else {
|
||||
Some(SplitSize::Percent((100.0 * size / max_percent) as usize))
|
||||
Some(SplitSize::Percent(
|
||||
((100.0 * size / max_percent) as usize).max(1),
|
||||
))
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -2462,6 +2497,217 @@ mod tests {
|
||||
assert_eq!(floating[0].border_style, Some(floating_border_style));
|
||||
}
|
||||
|
||||
fn round_trip_geom(x: usize, y: usize, rows: Dimension, cols: Dimension) -> PaneGeom {
|
||||
PaneGeom {
|
||||
x,
|
||||
y,
|
||||
rows,
|
||||
cols,
|
||||
stacked: None,
|
||||
is_pinned: false,
|
||||
logical_position: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_and_parse(
|
||||
global_layout_manifest: GlobalLayoutManifest,
|
||||
) -> (String, crate::input::layout::Layout) {
|
||||
let (kdl, _) = serialize_session_layout(global_layout_manifest).unwrap();
|
||||
let layout =
|
||||
crate::input::layout::Layout::from_kdl(&kdl, Some("layout".to_owned()), None, None)
|
||||
.unwrap_or_else(|e| panic!("failed to parse serialized layout: {:?}\n{}", e, kdl));
|
||||
(kdl, layout)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lone_pane_with_stale_stack_id_round_trips() {
|
||||
let mut lone_geom = round_trip_geom(0, 0, Dimension::fixed(10), Dimension::fixed(10));
|
||||
lone_geom.stacked = Some(3);
|
||||
let mut left_geom = round_trip_geom(0, 0, Dimension::fixed(10), Dimension::fixed(10));
|
||||
left_geom.stacked = Some(4);
|
||||
let right_geom = round_trip_geom(10, 0, Dimension::fixed(10), Dimension::fixed(10));
|
||||
let global_layout_manifest = GlobalLayoutManifest {
|
||||
tabs: vec![
|
||||
(
|
||||
"single".to_owned(),
|
||||
TabLayoutManifest {
|
||||
tiled_panes: vec![PaneLayoutManifest {
|
||||
geom: lone_geom,
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
"split".to_owned(),
|
||||
TabLayoutManifest {
|
||||
tiled_panes: vec![
|
||||
PaneLayoutManifest {
|
||||
geom: left_geom,
|
||||
..Default::default()
|
||||
},
|
||||
PaneLayoutManifest {
|
||||
geom: right_geom,
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
let (kdl, _layout) = serialize_and_parse(global_layout_manifest);
|
||||
assert!(!kdl.contains("expanded"), "{}", kdl);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stacked_panes_keep_expanded_flag_in_round_trip() {
|
||||
let mut top = round_trip_geom(0, 0, Dimension::fixed(1), Dimension::fixed(10));
|
||||
top.stacked = Some(0);
|
||||
let mut middle = round_trip_geom(0, 1, Dimension::fixed(10), Dimension::fixed(10));
|
||||
middle.stacked = Some(0);
|
||||
let global_layout_manifest = GlobalLayoutManifest {
|
||||
tabs: vec![(
|
||||
"stack".to_owned(),
|
||||
TabLayoutManifest {
|
||||
tiled_panes: vec![
|
||||
PaneLayoutManifest {
|
||||
geom: top,
|
||||
..Default::default()
|
||||
},
|
||||
PaneLayoutManifest {
|
||||
geom: middle,
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
)],
|
||||
..Default::default()
|
||||
};
|
||||
let (kdl, _layout) = serialize_and_parse(global_layout_manifest);
|
||||
assert!(kdl.contains("stacked=true"), "{}", kdl);
|
||||
assert!(kdl.contains("expanded=true"), "{}", kdl);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dollar_signs_in_paths_survive_a_round_trip() {
|
||||
use crate::input::command::RunCommand;
|
||||
let pane_cwd = PathBuf::from("/tmp/routes/$programId+");
|
||||
let command = PathBuf::from("/opt/$HOME/bin/tool");
|
||||
let global_cwd = PathBuf::from("/srv/$ZELLIJ_UNSET_TEST_VAR");
|
||||
let global_layout_manifest = GlobalLayoutManifest {
|
||||
global_cwd: Some(global_cwd.clone()),
|
||||
tabs: vec![(
|
||||
"tab".to_owned(),
|
||||
TabLayoutManifest {
|
||||
tiled_panes: vec![PaneLayoutManifest {
|
||||
geom: round_trip_geom(0, 0, Dimension::fixed(10), Dimension::fixed(10)),
|
||||
run: Some(Run::Command(RunCommand {
|
||||
command: command.clone(),
|
||||
..Default::default()
|
||||
})),
|
||||
cwd: Some(pane_cwd.clone()),
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
)],
|
||||
..Default::default()
|
||||
};
|
||||
let (_kdl, layout) = serialize_and_parse(global_layout_manifest);
|
||||
let (tiled, _floating) = layout
|
||||
.tabs()
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|(_, t, f)| (t, f))
|
||||
.unwrap();
|
||||
let pane = &tiled.children[0];
|
||||
match &pane.run {
|
||||
Some(Run::Command(run_command)) => {
|
||||
assert_eq!(run_command.command, command);
|
||||
assert_eq!(run_command.cwd, Some(pane_cwd));
|
||||
},
|
||||
other => panic!("unexpected run: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stacked_pane_template_round_trips_without_nested_stacks() {
|
||||
let default_layout = crate::input::layout::Layout::from_kdl(
|
||||
r#"
|
||||
layout {
|
||||
pane_template name="stack" stacked=true {
|
||||
children
|
||||
}
|
||||
stack {
|
||||
pane
|
||||
pane
|
||||
}
|
||||
swap_tiled_layout name="stacked" {
|
||||
tab {
|
||||
stack {
|
||||
pane
|
||||
pane
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
Some("layout".to_owned()),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let global_layout_manifest = GlobalLayoutManifest {
|
||||
default_layout: Box::new(default_layout),
|
||||
..Default::default()
|
||||
};
|
||||
let (kdl, layout) = serialize_and_parse(global_layout_manifest);
|
||||
assert!(!kdl.contains("expanded"), "{}", kdl);
|
||||
let (template_tiled, _) = layout.template.unwrap();
|
||||
let stack = &template_tiled.children[0];
|
||||
assert!(stack.children_are_stacked, "{}", kdl);
|
||||
assert_eq!(stack.children.len(), 2, "{}", kdl);
|
||||
assert!(stack.children.iter().all(|c| c.children.is_empty()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tiny_splits_are_never_serialized_as_zero_percent() {
|
||||
let mut tiny_rows = Dimension::percent(0.4);
|
||||
tiny_rows.set_inner(1);
|
||||
let mut big_rows = Dimension::percent(99.6);
|
||||
big_rows.set_inner(99);
|
||||
let mut tiny_floating_rows = Dimension::percent(0.4);
|
||||
tiny_floating_rows.set_inner(1);
|
||||
let global_layout_manifest = GlobalLayoutManifest {
|
||||
tabs: vec![(
|
||||
"tab".to_owned(),
|
||||
TabLayoutManifest {
|
||||
tiled_panes: vec![
|
||||
PaneLayoutManifest {
|
||||
geom: round_trip_geom(0, 0, tiny_rows, Dimension::fixed(10)),
|
||||
..Default::default()
|
||||
},
|
||||
PaneLayoutManifest {
|
||||
geom: round_trip_geom(0, 1, big_rows, Dimension::fixed(10)),
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
floating_panes: vec![PaneLayoutManifest {
|
||||
geom: round_trip_geom(2, 2, tiny_floating_rows, Dimension::fixed(10)),
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
)],
|
||||
..Default::default()
|
||||
};
|
||||
let (kdl, _layout) = serialize_and_parse(global_layout_manifest);
|
||||
assert!(!kdl.contains("\"0%\""), "{}", kdl);
|
||||
}
|
||||
|
||||
fn get_dim(dim_hm: &Value) -> Dimension {
|
||||
let constr_str = dim_hm["constraint"].to_string();
|
||||
let dim = if constr_str.contains("Fixed") {
|
||||
|
||||
@@ -463,15 +463,16 @@ pub fn resurrection_layout(session_name_to_resurrect: &str) -> Result<Option<Lay
|
||||
) {
|
||||
Ok(layout) => Ok(Some(layout)),
|
||||
Err(e) => {
|
||||
let error_text = crate::input::keybind_presets::config_error_text(&e);
|
||||
log::error!(
|
||||
"Failed to parse resurrection layout file {}: {}",
|
||||
layout_file_name.display(),
|
||||
e
|
||||
error_text
|
||||
);
|
||||
return Err(format!(
|
||||
"Failed to parse resurrection layout file {}: {}.",
|
||||
layout_file_name.display(),
|
||||
e
|
||||
error_text
|
||||
));
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user