use tracing::debug;
use crate::cmd::Cmd;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Rect {
w: u16,
h: u16,
x: u16,
y: u16,
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum LayoutNode {
Leaf {
rect: Rect,
pane_id: u32,
},
HSplit {
rect: Rect,
children: Vec<LayoutNode>,
},
VSplit {
rect: Rect,
children: Vec<LayoutNode>,
},
}
impl LayoutNode {
fn rect(&self) -> &Rect {
match self {
LayoutNode::Leaf { rect, .. }
| LayoutNode::HSplit { rect, .. }
| LayoutNode::VSplit { rect, .. } => rect,
}
}
fn rect_mut(&mut self) -> &mut Rect {
match self {
LayoutNode::Leaf { rect, .. }
| LayoutNode::HSplit { rect, .. }
| LayoutNode::VSplit { rect, .. } => rect,
}
}
fn width(&self) -> u16 {
self.rect().w
}
}
struct Parser<'a> {
input: &'a [u8],
pos: usize,
}
impl<'a> Parser<'a> {
fn new(input: &'a str) -> Self {
Self {
input: input.as_bytes(),
pos: 0,
}
}
fn peek(&self) -> Option<u8> {
self.input.get(self.pos).copied()
}
fn advance(&mut self) {
self.pos += 1;
}
fn expect(&mut self, ch: u8) -> Option<()> {
if self.peek() == Some(ch) {
self.advance();
Some(())
} else {
None
}
}
fn parse_num<T: std::str::FromStr>(&mut self) -> Option<T> {
let start = self.pos;
while self.peek().is_some_and(|b| b.is_ascii_digit()) {
self.advance();
}
if self.pos == start {
return None;
}
std::str::from_utf8(&self.input[start..self.pos])
.ok()?
.parse()
.ok()
}
fn parse_rect(&mut self) -> Option<Rect> {
let w = self.parse_num()?;
self.expect(b'x')?;
let h = self.parse_num()?;
self.expect(b',')?;
let x = self.parse_num()?;
self.expect(b',')?;
let y = self.parse_num()?;
Some(Rect { w, h, x, y })
}
fn parse_node(&mut self) -> Option<LayoutNode> {
let rect = self.parse_rect()?;
match self.peek() {
Some(b'{') => {
self.advance();
let children = self.parse_children(b'}')?;
Some(LayoutNode::HSplit { rect, children })
}
Some(b'[') => {
self.advance();
let children = self.parse_children(b']')?;
Some(LayoutNode::VSplit { rect, children })
}
Some(b',') => {
self.advance();
let pane_id = self.parse_num()?;
Some(LayoutNode::Leaf { rect, pane_id })
}
_ => None,
}
}
fn parse_children(&mut self, close: u8) -> Option<Vec<LayoutNode>> {
let mut children = Vec::new();
loop {
children.push(self.parse_node()?);
match self.peek() {
Some(c) if c == close => {
self.advance();
return Some(children);
}
Some(b',') => {
self.advance();
}
_ => return None,
}
}
}
}
fn parse_layout(layout: &str) -> Option<LayoutNode> {
let body = layout.get(5..)?;
if layout.as_bytes().get(4).copied().is_none_or(|b| b != b',') {
return None;
}
let mut parser = Parser::new(body);
let node = parser.parse_node()?;
if parser.pos == parser.input.len() {
Some(node)
} else {
None
}
}
fn serialize_node(node: &LayoutNode) -> String {
let mut out = String::new();
write_node(node, &mut out);
out
}
fn write_node(node: &LayoutNode, out: &mut String) {
use std::fmt::Write;
let r = node.rect();
let _ = write!(out, "{}x{},{},{}", r.w, r.h, r.x, r.y);
match node {
LayoutNode::Leaf { pane_id, .. } => {
let _ = write!(out, ",{}", pane_id);
}
LayoutNode::HSplit { children, .. } => {
out.push('{');
for (i, child) in children.iter().enumerate() {
if i > 0 {
out.push(',');
}
write_node(child, out);
}
out.push('}');
}
LayoutNode::VSplit { children, .. } => {
out.push('[');
for (i, child) in children.iter().enumerate() {
if i > 0 {
out.push(',');
}
write_node(child, out);
}
out.push(']');
}
}
}
fn layout_checksum(layout: &str) -> u16 {
let mut csum: u16 = 0;
for &b in layout.as_bytes() {
csum = (csum >> 1) | ((csum & 1) << 15);
csum = csum.wrapping_add(b as u16);
}
csum
}
fn serialize_layout(root: &LayoutNode) -> String {
let body = serialize_node(root);
let checksum = layout_checksum(&body);
format!("{:04x},{}", checksum, body)
}
fn proportional_widths(old_widths: &[u16], available: u16) -> Vec<u16> {
let old_total: u16 = old_widths.iter().sum();
if old_total == 0 || old_widths.is_empty() {
return vec![0; old_widths.len()];
}
let mut remaining = available;
let last = old_widths.len() - 1;
old_widths
.iter()
.enumerate()
.map(|(i, &old_w)| {
if i == last {
remaining
} else {
let scaled = (old_w as f64 * available as f64 / old_total as f64).round() as u16;
let scaled = scaled.min(remaining);
remaining = remaining.saturating_sub(scaled);
scaled
}
})
.collect()
}
fn scale_width(node: &mut LayoutNode, new_w: u16, new_x: u16) {
let rect = node.rect_mut();
rect.w = new_w;
rect.x = new_x;
match node {
LayoutNode::HSplit { children, .. } => {
let seps = children.len().saturating_sub(1) as u16;
let old_widths: Vec<u16> = children.iter().map(|c| c.width()).collect();
let new_widths = proportional_widths(&old_widths, new_w.saturating_sub(seps));
let mut cx = new_x;
for (child, &child_w) in children.iter_mut().zip(&new_widths) {
scale_width(child, child_w, cx);
cx = cx.saturating_add(child_w).saturating_add(1);
}
}
LayoutNode::VSplit { children, .. } => {
for child in children {
scale_width(child, new_w, new_x);
}
}
LayoutNode::Leaf { .. } => {
}
}
}
pub(super) fn reflow_after_sidebar_add(window_id: &str, sidebar_pane_id: &str, sidebar_width: u16) {
let layout_str = match Cmd::new("tmux")
.args(&["display-message", "-t", window_id, "-p", "#{window_layout}"])
.run_and_capture_stdout()
{
Ok(s) => s.trim().to_string(),
Err(_) => return,
};
debug!(
window_id,
sidebar_pane_id,
sidebar_width,
layout = layout_str.as_str(),
"reflow: starting"
);
let mut root = match parse_layout(&layout_str) {
Some(node) => node,
None => {
debug!(
layout = layout_str.as_str(),
"reflow: failed to parse layout"
);
return;
}
};
let LayoutNode::HSplit { rect, children } = &mut root else {
debug!("reflow: root is not HSplit, skipping");
return;
};
let sidebar_num: u32 = sidebar_pane_id
.strip_prefix('%')
.and_then(|s| s.parse().ok())
.unwrap_or(0);
let sidebar_idx = children.iter().position(
|child| matches!(child, LayoutNode::Leaf { pane_id, .. } if *pane_id == sidebar_num),
);
let Some(sidebar_idx) = sidebar_idx else {
debug!(
sidebar_pane_id,
"reflow: sidebar pane not found among {} root children",
children.len()
);
return;
};
debug!(
sidebar_idx,
root_children = children.len(),
"reflow: found sidebar"
);
children[sidebar_idx].rect_mut().w = sidebar_width;
children[sidebar_idx].rect_mut().x = 0;
let window_w = rect.w;
let num_content = children.len() - 1;
let total_seps = (children.len() as u16).saturating_sub(1);
let available = window_w
.saturating_sub(sidebar_width)
.saturating_sub(total_seps);
let content_indices: Vec<usize> = (0..children.len()).filter(|&i| i != sidebar_idx).collect();
let old_widths: Vec<u16> = content_indices
.iter()
.map(|&i| children[i].width())
.collect();
debug!(window_w, available, num_content, "reflow: scaling content");
if available == 0 {
return;
}
let new_widths = proportional_widths(&old_widths, available);
let mut cx = sidebar_width + 1;
for (&idx, &new_w) in content_indices.iter().zip(&new_widths) {
scale_width(&mut children[idx], new_w, cx);
cx = cx.saturating_add(new_w).saturating_add(1);
}
let new_layout = serialize_layout(&root);
debug!(
window_id,
old = layout_str.as_str(),
new = new_layout.as_str(),
"reflow: applying"
);
let _ = Cmd::new("tmux")
.args(&["select-layout", "-t", window_id, &new_layout])
.run();
}
fn prune_pane(node: LayoutNode, target: u32) -> Option<LayoutNode> {
match node {
LayoutNode::Leaf { pane_id, .. } if pane_id == target => None,
leaf @ LayoutNode::Leaf { .. } => Some(leaf),
LayoutNode::HSplit { rect, children } => {
let mut children: Vec<_> = children
.into_iter()
.filter_map(|child| prune_pane(child, target))
.collect();
match children.len() {
0 => None,
1 => Some(children.remove(0)),
_ => Some(LayoutNode::HSplit { rect, children }),
}
}
LayoutNode::VSplit { rect, children } => {
let mut children: Vec<_> = children
.into_iter()
.filter_map(|child| prune_pane(child, target))
.collect();
match children.len() {
0 => None,
1 => Some(children.remove(0)),
_ => Some(LayoutNode::VSplit { rect, children }),
}
}
}
}
pub(super) fn layout_after_sidebar_remove(
window_id: &str,
sidebar_pane_id: &str,
) -> Option<String> {
let layout_str = Cmd::new("tmux")
.args(&["display-message", "-t", window_id, "-p", "#{window_layout}"])
.run_and_capture_stdout()
.ok()?;
let layout_str = layout_str.trim();
debug!(
window_id,
sidebar_pane_id,
layout = layout_str,
"layout_after_sidebar_remove: starting"
);
let root = parse_layout(layout_str)?;
let window_rect = *root.rect();
let sidebar_num: u32 = sidebar_pane_id
.strip_prefix('%')
.and_then(|s| s.parse().ok())?;
let mut content = prune_pane(root, sidebar_num)?;
scale_width(&mut content, window_rect.w, window_rect.x);
let result = serialize_layout(&content);
debug!(
window_id,
old = layout_str,
new = result.as_str(),
"layout_after_sidebar_remove: computed"
);
Some(result)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_single_pane() {
let layout = "1234,80x24,0,0,42";
let node = parse_layout(layout).unwrap();
assert_eq!(
node,
LayoutNode::Leaf {
rect: Rect {
w: 80,
h: 24,
x: 0,
y: 0
},
pane_id: 42,
}
);
}
#[test]
fn test_parse_hsplit() {
let layout = "abcd,80x24,0,0{40x24,0,0,1,39x24,41,0,2}";
let node = parse_layout(layout).unwrap();
match node {
LayoutNode::HSplit { rect, children } => {
assert_eq!(rect.w, 80);
assert_eq!(children.len(), 2);
assert_eq!(children[0].width(), 40);
assert_eq!(children[1].width(), 39);
}
_ => panic!("expected HSplit"),
}
}
#[test]
fn test_parse_vsplit() {
let layout = "abcd,80x24,0,0[80x12,0,0,1,80x11,0,13,2]";
let node = parse_layout(layout).unwrap();
match node {
LayoutNode::VSplit { rect, children } => {
assert_eq!(rect.h, 24);
assert_eq!(children.len(), 2);
assert_eq!(children[0].rect().h, 12);
assert_eq!(children[1].rect().h, 11);
}
_ => panic!("expected VSplit"),
}
}
#[test]
fn test_parse_nested() {
let layout = "123a,186x44,0,0{93x44,0,0[93x22,0,0,1189,93x21,0,23,1394],92x44,94,0,1387}";
let node = parse_layout(layout).unwrap();
match node {
LayoutNode::HSplit { children, .. } => {
assert_eq!(children.len(), 2);
match &children[0] {
LayoutNode::VSplit { children: vc, .. } => {
assert_eq!(vc.len(), 2);
}
_ => panic!("expected VSplit as first child"),
}
assert!(matches!(
&children[1],
LayoutNode::Leaf { pane_id: 1387, .. }
));
}
_ => panic!("expected HSplit"),
}
}
#[test]
fn test_roundtrip_real_layouts() {
let layouts = [
"9d0a,373x79,0,0{205x79,0,0[205x39,0,0,1070,205x39,0,40,1073],167x79,206,0,1072}",
"6804,373x79,0,0{205x79,0,0,1075,167x79,206,0,1077}",
"1bd3,373x79,0,0{242x79,0,0,510,130x79,243,0,532}",
"f6ce,373x79,0,0{211x79,0,0,509,161x79,212,0,986}",
"7e05,373x79,0,0{205x79,0,0,988,167x79,206,0,989}",
"37ce,373x79,0,0{221x79,0,0,521,151x79,222,0,528}",
"c6bc,373x79,0,0{212x79,0,0,634,160x79,213,0[160x20,213,0,636,160x58,213,21,637]}",
"f64a,373x79,0,0,640",
"f652,373x79,0,0,648",
"f651,373x79,0,0,666",
"ec38,373x79,0,0,67",
"0e04,186x44,0,0{100x44,0,0[100x21,0,0,980,100x22,0,22,817],85x44,101,0[85x21,101,0,985,85x22,101,22,1169]}",
"f910,186x44,0,0{91x44,0,0,350,94x44,92,0[94x21,92,0,512,94x22,92,22,1074]}",
"123a,186x44,0,0{93x44,0,0[93x22,0,0,1189,93x21,0,23,1394],92x44,94,0,1387}",
"7fb5,186x44,0,0{25x44,0,0,1395,91x44,26,0,1196,68x44,118,0,1307}",
"184f,186x44,0,0,1396",
];
for layout in layouts {
let node = parse_layout(layout).expect(&format!("failed to parse: {}", layout));
let result = serialize_layout(&node);
assert_eq!(result, layout, "roundtrip failed");
}
}
#[test]
fn test_checksum_known_values() {
let cases = [
(
"373x79,0,0{205x79,0,0[205x39,0,0,1070,205x39,0,40,1073],167x79,206,0,1072}",
"9d0a",
),
("373x79,0,0{205x79,0,0,1075,167x79,206,0,1077}", "6804"),
("373x79,0,0,640", "f64a"),
(
"186x44,0,0{93x44,0,0[93x22,0,0,1189,93x21,0,23,1394],92x44,94,0,1387}",
"123a",
),
];
for (body, expected_hex) in cases {
let expected = u16::from_str_radix(expected_hex, 16).unwrap();
assert_eq!(
layout_checksum(body),
expected,
"checksum mismatch for: {}",
body
);
}
}
#[test]
fn test_scale_width_leaf() {
let mut node = LayoutNode::Leaf {
rect: Rect {
w: 100,
h: 50,
x: 0,
y: 0,
},
pane_id: 1,
};
scale_width(&mut node, 80, 10);
assert_eq!(node.rect().w, 80);
assert_eq!(node.rect().x, 10);
assert_eq!(node.rect().h, 50); }
#[test]
fn test_scale_width_hsplit_proportional() {
let mut node = LayoutNode::HSplit {
rect: Rect {
w: 200,
h: 50,
x: 0,
y: 0,
},
children: vec![
LayoutNode::Leaf {
rect: Rect {
w: 100,
h: 50,
x: 0,
y: 0,
},
pane_id: 1,
},
LayoutNode::Leaf {
rect: Rect {
w: 99,
h: 50,
x: 101,
y: 0,
},
pane_id: 2,
},
],
};
scale_width(&mut node, 150, 30);
assert_eq!(node.rect().w, 150);
assert_eq!(node.rect().x, 30);
let children = match &node {
LayoutNode::HSplit { children, .. } => children,
_ => panic!(),
};
assert_eq!(children[0].width(), 75);
assert_eq!(children[1].width(), 74);
assert_eq!(children[0].rect().x, 30);
assert_eq!(children[1].rect().x, 106);
}
#[test]
fn test_scale_width_vsplit() {
let mut node = LayoutNode::VSplit {
rect: Rect {
w: 100,
h: 50,
x: 0,
y: 0,
},
children: vec![
LayoutNode::Leaf {
rect: Rect {
w: 100,
h: 24,
x: 0,
y: 0,
},
pane_id: 1,
},
LayoutNode::Leaf {
rect: Rect {
w: 100,
h: 25,
x: 0,
y: 25,
},
pane_id: 2,
},
],
};
scale_width(&mut node, 80, 20);
let children = match &node {
LayoutNode::VSplit { children, .. } => children,
_ => panic!(),
};
assert_eq!(children[0].width(), 80);
assert_eq!(children[1].width(), 80);
assert_eq!(children[0].rect().x, 20);
assert_eq!(children[1].rect().x, 20);
assert_eq!(children[0].rect().h, 24);
assert_eq!(children[1].rect().h, 25);
}
#[test]
fn test_scale_sidebar_plus_vsplit_content() {
let layout =
"0000,186x44,0,0{35x44,0,0,999,150x44,36,0[150x22,36,0,1189,150x21,36,23,1394]}";
let mut root = parse_layout(layout).unwrap();
if let LayoutNode::HSplit { children, .. } = &mut root {
let content_w = 186u16.saturating_sub(35).saturating_sub(1);
scale_width(&mut children[1], content_w, 36);
assert_eq!(children[1].width(), content_w);
if let LayoutNode::VSplit { children: vc, .. } = &children[1] {
assert_eq!(vc[0].width(), content_w);
assert_eq!(vc[1].width(), content_w);
}
}
}
#[test]
fn test_reflow_two_children() {
let layout = "0000,200x50,0,0{30x50,0,0,100,169x50,31,0{60x50,31,0,101,79x50,92,0,102}}";
let mut root = parse_layout(layout).unwrap();
if let LayoutNode::HSplit { children, .. } = &mut root {
assert_eq!(children.len(), 2);
let content_w = 200u16 - 30 - 1; scale_width(&mut children[1], content_w, 31);
assert_eq!(children[1].width(), content_w);
if let LayoutNode::HSplit {
children: content, ..
} = &children[1]
{
let sum: u16 = content.iter().map(|c| c.width()).sum();
assert_eq!(sum, 168); assert!(content[0].width() > 70 && content[0].width() < 76);
assert!(content[1].width() > 92 && content[1].width() < 98);
}
}
}
#[test]
fn test_reflow_three_children_at_root() {
let layout =
"0000,186x44,0,0{25x44,0,0,999,75x44,26,0[75x22,26,0,1,75x21,26,23,2],85x44,102,0,3}";
let mut root = parse_layout(layout).unwrap();
if let LayoutNode::HSplit { rect, children } = &mut root {
assert_eq!(children.len(), 3);
let sidebar_width: u16 = 25;
children[0].rect_mut().w = sidebar_width;
children[0].rect_mut().x = 0;
let total_seps = 2u16;
let available = rect.w - sidebar_width - total_seps;
assert_eq!(available, 159);
let old_total: u16 = 75 + 85;
let mut remaining = available;
let mut cx = sidebar_width + 1;
let scaled = (75.0 * available as f64 / old_total as f64).round() as u16;
remaining -= scaled;
scale_width(&mut children[1], scaled, cx);
cx += scaled + 1;
scale_width(&mut children[2], remaining, cx);
let w1 = children[1].width();
let w2 = children[2].width();
assert_eq!(w1 + w2, available);
let original_ratio = 75.0 / 85.0;
let result_ratio = w1 as f64 / w2 as f64;
assert!(
(result_ratio - original_ratio).abs() < 0.05,
"proportions not preserved: original={:.2}, result={:.2}",
original_ratio,
result_ratio
);
if let LayoutNode::VSplit { children: vc, .. } = &children[1] {
assert_eq!(vc[0].width(), w1);
assert_eq!(vc[1].width(), w1);
}
}
}
#[test]
fn test_prune_removes_target_leaf() {
let node = LayoutNode::Leaf {
rect: Rect {
w: 80,
h: 24,
x: 0,
y: 0,
},
pane_id: 42,
};
assert_eq!(prune_pane(node, 42), None);
}
#[test]
fn test_prune_keeps_other_leaf() {
let node = LayoutNode::Leaf {
rect: Rect {
w: 80,
h: 24,
x: 0,
y: 0,
},
pane_id: 42,
};
assert!(prune_pane(node, 99).is_some());
}
#[test]
fn test_prune_collapses_single_child_hsplit() {
let node = LayoutNode::HSplit {
rect: Rect {
w: 186,
h: 44,
x: 0,
y: 0,
},
children: vec![
LayoutNode::Leaf {
rect: Rect {
w: 25,
h: 44,
x: 0,
y: 0,
},
pane_id: 999,
},
LayoutNode::Leaf {
rect: Rect {
w: 160,
h: 44,
x: 26,
y: 0,
},
pane_id: 100,
},
],
};
let result = prune_pane(node, 999).unwrap();
assert!(matches!(result, LayoutNode::Leaf { pane_id: 100, .. }));
}
#[test]
fn test_prune_preserves_multi_child_hsplit() {
let node = LayoutNode::HSplit {
rect: Rect {
w: 186,
h: 44,
x: 0,
y: 0,
},
children: vec![
LayoutNode::Leaf {
rect: Rect {
w: 25,
h: 44,
x: 0,
y: 0,
},
pane_id: 999,
},
LayoutNode::Leaf {
rect: Rect {
w: 80,
h: 44,
x: 26,
y: 0,
},
pane_id: 1,
},
LayoutNode::Leaf {
rect: Rect {
w: 79,
h: 44,
x: 107,
y: 0,
},
pane_id: 2,
},
],
};
let result = prune_pane(node, 999).unwrap();
match result {
LayoutNode::HSplit { children, .. } => {
assert_eq!(children.len(), 2);
assert!(matches!(&children[0], LayoutNode::Leaf { pane_id: 1, .. }));
assert!(matches!(&children[1], LayoutNode::Leaf { pane_id: 2, .. }));
}
_ => panic!("expected HSplit"),
}
}
#[test]
fn test_remove_sidebar_two_content_panes_fill_window() {
let layout = "0000,186x44,0,0{25x44,0,0,999,80x44,26,0,100,79x44,107,0,101}";
let root = parse_layout(layout).unwrap();
let window_w = root.rect().w;
let mut content = prune_pane(root, 999).unwrap();
scale_width(&mut content, window_w, 0);
match &content {
LayoutNode::HSplit { rect, children } => {
assert_eq!(rect.w, 186);
assert_eq!(children.len(), 2);
let w0 = children[0].width();
let w1 = children[1].width();
assert_eq!(w0 + w1 + 1, 186);
assert!(
(w0 as i32 - w1 as i32).abs() <= 1,
"panes should be near-equal: {} vs {}",
w0,
w1,
);
}
_ => panic!("expected HSplit"),
}
}
#[test]
fn test_remove_sidebar_vsplit_content_fills_window() {
let layout = "0000,186x44,0,0{25x44,0,0,999,160x44,26,0[160x22,26,0,100,160x21,26,23,101]}";
let root = parse_layout(layout).unwrap();
let window_w = root.rect().w;
let mut content = prune_pane(root, 999).unwrap();
scale_width(&mut content, window_w, 0);
match &content {
LayoutNode::VSplit { rect, children } => {
assert_eq!(rect.w, 186);
assert_eq!(children[0].width(), 186);
assert_eq!(children[1].width(), 186);
}
_ => panic!("expected VSplit"),
}
}
#[test]
fn test_remove_sidebar_nested_content_proportional() {
let layout =
"0000,186x44,0,0{25x44,0,0,999,111x44,26,0{60x44,26,0,1,50x44,87,0,2},48x44,138,0,3}";
let root = parse_layout(layout).unwrap();
let window_w = root.rect().w;
let mut content = prune_pane(root, 999).unwrap();
scale_width(&mut content, window_w, 0);
match &content {
LayoutNode::HSplit { rect, children } => {
assert_eq!(rect.w, 186);
assert_eq!(children.len(), 2);
let w0 = children[0].width();
let w1 = children[1].width();
assert_eq!(w0 + w1 + 1, 186);
let orig_ratio = 111.0 / 48.0;
let result_ratio = w0 as f64 / w1 as f64;
assert!(
(result_ratio - orig_ratio).abs() < 0.1,
"proportions not preserved: original={:.2}, result={:.2}",
orig_ratio,
result_ratio,
);
}
_ => panic!("expected HSplit"),
}
}
#[test]
fn test_remove_sidebar_single_content_fills_window() {
let layout = "0000,186x44,0,0{25x44,0,0,999,160x44,26,0,100}";
let root = parse_layout(layout).unwrap();
let window_w = root.rect().w;
let mut content = prune_pane(root, 999).unwrap();
scale_width(&mut content, window_w, 0);
match &content {
LayoutNode::Leaf { rect, pane_id } => {
assert_eq!(*pane_id, 100);
assert_eq!(rect.w, 186);
assert_eq!(rect.x, 0);
}
_ => panic!("expected Leaf"),
}
}
}