use super::PaneManager;
use crate::pane::types::{PaneId, PaneNode, SplitDirection};
use par_term_config::SplitBalance;
fn chain_units(node: &PaneNode, dir: SplitDirection) -> usize {
match node {
PaneNode::Split {
direction,
first,
second,
..
} if *direction == dir => chain_units(first, dir) + chain_units(second, dir),
_ => 1,
}
}
fn balance_chain(node: &mut PaneNode, dir: SplitDirection) {
if let PaneNode::Split {
direction,
ratio,
first,
second,
} = node
&& *direction == dir
{
let a = chain_units(first, dir) as f32;
let b = chain_units(second, dir) as f32;
*ratio = a / (a + b);
balance_chain(first, dir);
balance_chain(second, dir);
}
}
fn path_to(node: &PaneNode, pane: PaneId) -> Option<Vec<bool>> {
match node {
PaneNode::Leaf(p) => (p.id == pane).then(Vec::new),
PaneNode::Split { first, second, .. } => {
if let Some(mut path) = path_to(first, pane) {
path.insert(0, true);
Some(path)
} else {
let mut path = path_to(second, pane)?;
path.insert(0, false);
Some(path)
}
}
}
}
fn descend<'a>(mut node: &'a mut PaneNode, path: &[bool]) -> &'a mut PaneNode {
for &go_first in path {
match node {
PaneNode::Split { first, second, .. } => {
node = if go_first { first } else { second };
}
PaneNode::Leaf(_) => break,
}
}
node
}
fn direction_of(node: &PaneNode) -> Option<SplitDirection> {
match node {
PaneNode::Split { direction, .. } => Some(*direction),
PaneNode::Leaf(_) => None,
}
}
impl PaneManager {
pub fn balance_after_split(&mut self, new_pane: PaneId, balance: SplitBalance) -> bool {
match balance {
SplitBalance::None => false,
SplitBalance::All => self.equalize(),
SplitBalance::Siblings => self.guard_ratios(|pm| {
let Some(root) = pm.root.as_mut() else {
return;
};
let Some(path) = path_to(root, new_pane) else {
return;
};
let Some(parent_len) = path.len().checked_sub(1) else {
return;
};
let Some(dir) = direction_of(descend(root, &path[..parent_len])) else {
return;
};
let mut top = parent_len;
while top > 0 && direction_of(descend(root, &path[..top - 1])) == Some(dir) {
top -= 1;
}
balance_chain(descend(root, &path[..top]), dir);
}),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pane::types::{Pane, PaneBounds};
use par_term_terminal::TerminalManager;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use tokio::sync::RwLock;
fn stub(id: PaneId) -> Pane {
Pane::new_wrapping_terminal(
id,
Arc::new(RwLock::new(
TerminalManager::new_with_scrollback(20, 5, 0).expect("terminal"),
)),
None,
Arc::new(AtomicBool::new(false)),
)
}
fn widths(pm: &PaneManager) -> Vec<(PaneId, f32)> {
pm.all_panes()
.iter()
.map(|p| (p.id, p.bounds.width.round()))
.collect()
}
fn manager() -> PaneManager {
let mut pm = PaneManager::new();
pm.set_divider_width(0.0);
pm.set_root(PaneNode::split(
SplitDirection::Vertical,
0.5,
PaneNode::split(
SplitDirection::Horizontal,
0.5,
PaneNode::leaf(stub(1)),
PaneNode::leaf(stub(4)),
),
PaneNode::split(
SplitDirection::Vertical,
0.5,
PaneNode::leaf(stub(2)),
PaneNode::leaf(stub(3)),
),
));
pm.set_bounds(PaneBounds::new(0.0, 0.0, 900.0, 400.0));
pm
}
#[test]
fn none_leaves_the_layout_alone() {
let mut pm = manager();
assert!(!pm.balance_after_split(3, SplitBalance::None));
assert_eq!(
widths(&pm),
vec![(1, 450.0), (4, 450.0), (2, 225.0), (3, 225.0)]
);
}
#[test]
fn siblings_evens_the_run_beside_the_new_pane() {
let mut pm = manager();
assert!(pm.balance_after_split(3, SplitBalance::Siblings));
assert_eq!(
widths(&pm),
vec![(1, 300.0), (4, 300.0), (2, 300.0), (3, 300.0)]
);
}
#[test]
fn siblings_on_a_stacked_split_touches_only_the_stack() {
let mut pm = manager();
assert!(pm.balance_after_split(4, SplitBalance::Siblings));
assert_eq!(
widths(&pm),
vec![(1, 450.0), (4, 450.0), (2, 225.0), (3, 225.0)]
);
}
#[test]
fn all_equalizes_the_tab() {
let mut pm = manager();
assert!(pm.balance_after_split(3, SplitBalance::All));
assert_eq!(
widths(&pm),
vec![(1, 450.0), (4, 450.0), (2, 225.0), (3, 225.0)]
);
let mut pm = PaneManager::new();
pm.set_divider_width(0.0);
pm.set_root(PaneNode::split(
SplitDirection::Vertical,
0.5,
PaneNode::leaf(stub(1)),
PaneNode::split(
SplitDirection::Vertical,
0.5,
PaneNode::leaf(stub(2)),
PaneNode::leaf(stub(3)),
),
));
pm.set_bounds(PaneBounds::new(0.0, 0.0, 900.0, 400.0));
assert!(pm.balance_after_split(3, SplitBalance::All));
assert_eq!(widths(&pm), vec![(1, 300.0), (2, 300.0), (3, 300.0)]);
}
}