1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-27 02:25:28 +03:00

panes: adopt zipper based bintree for managing panes

This simplifies the logic in the tab.rs module
This commit is contained in:
Wez Furlong 2020-07-04 10:54:39 -07:00
parent c2c788b41d
commit ec468acb40
3 changed files with 241 additions and 213 deletions

View File

@ -142,6 +142,24 @@ where
} }
} }
pub struct ParentIterator<'a, L, N> {
path: &'a Path<L, N>,
}
impl<'a, L, N> std::iter::Iterator for ParentIterator<'a, L, N> {
type Item = &'a Option<N>;
fn next(&mut self) -> Option<Self::Item> {
match self.path {
Path::Top => None,
Path::Left { data, up, .. } | Path::Right { data, up, .. } => {
self.path = &*up;
Some(data)
}
}
}
}
impl<L, N> Tree<L, N> { impl<L, N> Tree<L, N> {
/// Construct a new empty tree /// Construct a new empty tree
pub fn new() -> Self { pub fn new() -> Self {
@ -182,6 +200,22 @@ impl<L, N> Cursor<L, N> {
} }
} }
/// Returns true if the current position is the left child of its parent
pub fn is_left(&self) -> bool {
match &*self.path {
Path::Left { .. } => true,
_ => false,
}
}
/// Returns true if the current position is the right child of its parent
pub fn is_right(&self) -> bool {
match &*self.path {
Path::Right { .. } => true,
_ => false,
}
}
/// If the current position is the root of the empty tree, /// If the current position is the root of the empty tree,
/// assign an initial leaf value. /// assign an initial leaf value.
/// Consumes the cursor and returns a new cursor representing /// Consumes the cursor and returns a new cursor representing
@ -216,6 +250,13 @@ impl<L, N> Cursor<L, N> {
} }
} }
/// Return an iterator that will visit the chain of nodes leading
/// to the root from the current position and yield their node
/// data at each step of iteration.
pub fn path_to_root(&self) -> ParentIterator<L, N> {
ParentIterator { path: &*self.path }
}
/// If the current position is not a leaf node, assign the /// If the current position is not a leaf node, assign the
/// node data to the supplied value. /// node data to the supplied value.
/// Consumes the cursor and returns a new cursor representing the /// Consumes the cursor and returns a new cursor representing the
@ -368,6 +409,20 @@ impl<L, N> Cursor<L, N> {
} }
} }
/// Move to the nth (preorder) leaf from the current position.
pub fn go_to_nth_leaf(mut self, n: usize) -> Result<Self, Self> {
let mut next = 0;
loop {
if self.is_leaf() {
if next == n {
return Ok(self);
}
next += 1;
}
self = self.preorder_next()?;
}
}
/// Consume the cursor and return the root of the Tree /// Consume the cursor and return the root of the Tree
pub fn tree(mut self) -> Tree<L, N> { pub fn tree(mut self) -> Tree<L, N> {
loop { loop {

View File

@ -62,7 +62,7 @@ pub mod ssh;
pub mod serial; pub mod serial;
/// Represents the size of the visible display area in the pty /// Represents the size of the visible display area in the pty
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct PtySize { pub struct PtySize {
/// The number of lines of text /// The number of lines of text

View File

@ -2,6 +2,7 @@ use crate::mux::domain::DomainId;
use crate::mux::renderable::Renderable; use crate::mux::renderable::Renderable;
use crate::mux::Mux; use crate::mux::Mux;
use async_trait::async_trait; use async_trait::async_trait;
use bintree::Tree;
use downcast_rs::{impl_downcast, Downcast}; use downcast_rs::{impl_downcast, Downcast};
use portable_pty::PtySize; use portable_pty::PtySize;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -92,7 +93,7 @@ pub struct SearchResult {
/// At this time only a single pane is supported /// At this time only a single pane is supported
pub struct Tab { pub struct Tab {
id: TabId, id: TabId,
pane: RefCell<Option<PaneNode>>, pane: RefCell<Option<Tree<Rc<dyn Pane>, SplitDirectionAndSize>>>,
size: RefCell<PtySize>, size: RefCell<PtySize>,
active: RefCell<usize>, active: RefCell<usize>,
} }
@ -117,133 +118,28 @@ pub struct PositionedPane {
pub pane: Rc<dyn Pane>, pub pane: Rc<dyn Pane>,
} }
/// A tab contains a tree of PaneNode's. #[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[derive(Clone)] pub enum SplitDirection {
enum PaneNode { Horizontal,
/// This node is filled with a single Pane Vertical,
Single(Rc<dyn Pane>),
/// This node is split horizontally in two.
HorizontalSplit {
left: Box<PaneNode>,
left_width: usize,
right: Box<PaneNode>,
},
/// This node is split vertically in two.
VerticalSplit {
top: Box<PaneNode>,
top_height: usize,
bottom: Box<PaneNode>,
},
} }
impl PaneNode { /// The size is of the (first, second) child of the split
/// Returns true if this node or any of its children are #[derive(Debug, Clone, Copy, Eq, PartialEq)]
/// alive. Stops evaluating as soon as it identifies that struct SplitDirectionAndSize {
/// something is alive. direction: SplitDirection,
pub fn is_alive(&self) -> bool { /// Offset relative to container
match self { left: usize,
PaneNode::Single(p) => !p.is_dead(), top: usize,
PaneNode::HorizontalSplit { left, right, .. } => left.is_alive() || right.is_alive(), first: PtySize,
PaneNode::VerticalSplit { top, bottom, .. } => top.is_alive() || bottom.is_alive(), second: PtySize,
}
}
/// Returns a ref to the PaneNode::Single that contains a pane
/// given its topological index.
/// The if topological index is invalid, returns None.
fn node_by_index_mut(
&mut self,
wanted_index: usize,
current_index: &mut usize,
) -> Option<&mut Self> {
match self {
PaneNode::Single(_) => {
if wanted_index == *current_index {
Some(self)
} else {
*current_index += 1;
None
}
}
PaneNode::HorizontalSplit { left, right, .. } => {
if let Some(found) = left.node_by_index_mut(wanted_index, current_index) {
Some(found)
} else {
right.node_by_index_mut(wanted_index, current_index)
}
}
PaneNode::VerticalSplit { top, bottom, .. } => {
if let Some(found) = top.node_by_index_mut(wanted_index, current_index) {
Some(found)
} else {
bottom.node_by_index_mut(wanted_index, current_index)
}
}
}
}
/// Recursively Walk to compute the positioning information
fn walk(
&self,
active_index: usize,
x: usize,
y: usize,
width: usize,
height: usize,
panes: &mut Vec<PositionedPane>,
) {
match self {
PaneNode::Single(p) => {
let index = panes.len();
panes.push(PositionedPane {
index,
is_active: index == active_index,
left: x,
top: y,
width,
height,
pane: Rc::clone(p),
});
}
PaneNode::HorizontalSplit {
left,
left_width,
right,
} => {
left.walk(active_index, x, y, *left_width, height, panes);
right.walk(
active_index,
x + *left_width,
y,
width.saturating_sub(*left_width),
height,
panes,
);
}
PaneNode::VerticalSplit {
top,
top_height,
bottom,
} => {
top.walk(active_index, x, y, width, *top_height, panes);
bottom.walk(
active_index,
x,
y + *top_height,
width,
height.saturating_sub(*top_height),
panes,
);
}
}
}
} }
impl Tab { impl Tab {
pub fn new(size: &PtySize) -> Self { pub fn new(size: &PtySize) -> Self {
Self { Self {
id: TAB_ID.fetch_add(1, ::std::sync::atomic::Ordering::Relaxed), id: TAB_ID.fetch_add(1, ::std::sync::atomic::Ordering::Relaxed),
pane: RefCell::new(None), pane: RefCell::new(Some(Tree::new())),
size: RefCell::new(*size), size: RefCell::new(*size),
active: RefCell::new(0), active: RefCell::new(0),
} }
@ -253,17 +149,56 @@ impl Tab {
/// list of PositionedPane instances along with their positioning information. /// list of PositionedPane instances along with their positioning information.
pub fn iter_panes(&self) -> Vec<PositionedPane> { pub fn iter_panes(&self) -> Vec<PositionedPane> {
let mut panes = vec![]; let mut panes = vec![];
let size = *self.size.borrow(); let active_idx = *self.active.borrow();
let mut root = self.pane.borrow_mut();
let mut cursor = root.take().unwrap().cursor();
if let Some(pane) = self.pane.borrow().as_ref() { loop {
pane.walk( if cursor.is_leaf() {
*self.active.borrow(), let index = panes.len();
0, let mut left = 0usize;
0, let mut top = 0usize;
size.cols as _, let mut parent_size = None;
size.rows as _, let is_second = cursor.is_right();
&mut panes, for node in cursor.path_to_root() {
); if let Some(node) = node {
if parent_size.is_none() {
parent_size.replace(if is_second { node.second } else { node.first });
if is_second {
match node.direction {
SplitDirection::Vertical => top += node.first.rows as usize + 1,
SplitDirection::Horizontal => {
left += node.first.cols as usize + 1
}
}
}
}
left += node.left;
top += node.top;
}
}
let pane = Rc::clone(cursor.leaf_mut().unwrap());
let dims = parent_size.unwrap_or_else(|| *self.size.borrow());
panes.push(PositionedPane {
index,
is_active: index == active_idx,
left,
top,
width: dims.cols as _,
height: dims.rows as _,
pane,
});
}
match cursor.preorder_next() {
Ok(c) => cursor = c,
Err(c) => {
root.replace(c.tree());
break;
}
}
} }
panes panes
@ -274,11 +209,14 @@ impl Tab {
} }
pub fn is_dead(&self) -> bool { pub fn is_dead(&self) -> bool {
if let Some(pane) = self.pane.borrow().as_ref() { let panes = self.iter_panes();
!pane.is_alive() let mut dead_count = 0;
} else { for pos in &panes {
true if pos.pane.is_dead() {
dead_count += 1;
}
} }
dead_count == panes.len()
} }
pub fn get_active_pane(&self) -> Option<Rc<dyn Pane>> { pub fn get_active_pane(&self) -> Option<Rc<dyn Pane>> {
@ -292,9 +230,10 @@ impl Tab {
/// This is suitable when creating a new tab and then assigning /// This is suitable when creating a new tab and then assigning
/// the initial pane /// the initial pane
pub fn assign_pane(&self, pane: &Rc<dyn Pane>) { pub fn assign_pane(&self, pane: &Rc<dyn Pane>) {
self.pane match Tree::new().cursor().assign_top(Rc::clone(pane)) {
.borrow_mut() Ok(c) => *self.pane.borrow_mut() = Some(c.tree()),
.replace(PaneNode::Single(Rc::clone(pane))); Err(_) => panic!("tried to assign root pane to non-empty tree"),
}
} }
fn cell_dimensions(&self) -> PtySize { fn cell_dimensions(&self) -> PtySize {
@ -312,23 +251,48 @@ impl Tab {
/// The intent is to call this prior to spawning the new pane so that /// The intent is to call this prior to spawning the new pane so that
/// you can create it with the correct size. /// you can create it with the correct size.
/// May return None if the specified pane_index is invalid. /// May return None if the specified pane_index is invalid.
pub fn compute_split_size( fn compute_split_size(
&self, &self,
pane_index: usize, pane_index: usize,
direction: SplitDirection, direction: SplitDirection,
) -> Option<PtySize> { ) -> Option<SplitDirectionAndSize> {
let cell_dims = self.cell_dimensions(); let cell_dims = self.cell_dimensions();
self.iter_panes().iter().nth(pane_index).map(|pos| { self.iter_panes().iter().nth(pane_index).map(|pos| {
let (width, height) = match direction { fn split_dimension(dim: usize) -> (usize, usize) {
SplitDirection::Horizontal => (pos.width / 2, pos.height), let halved = dim / 2;
SplitDirection::Vertical => (pos.width, pos.height / 2), if halved * 2 == dim {
// Was an even size; we need to allow 1 cell to render
// the split UI, so make the newly created leaf slightly
// smaller
(halved, halved.saturating_sub(1))
} else {
(halved, halved)
}
}
let ((width1, width2), (height1, height2)) = match direction {
SplitDirection::Horizontal => {
(split_dimension(pos.width), (pos.height, pos.height))
}
SplitDirection::Vertical => ((pos.width, pos.width), split_dimension(pos.height)),
}; };
PtySize { SplitDirectionAndSize {
rows: height as _, direction,
cols: width as _, left: pos.left,
pixel_width: cell_dims.pixel_width * width as u16, top: pos.top,
pixel_height: cell_dims.pixel_height * height as u16, first: PtySize {
rows: height1 as _,
cols: width1 as _,
pixel_height: cell_dims.pixel_height * height1 as u16,
pixel_width: cell_dims.pixel_width * width1 as u16,
},
second: PtySize {
rows: height2 as _,
cols: width2 as _,
pixel_height: cell_dims.pixel_height * height2 as u16,
pixel_width: cell_dims.pixel_width * width2 as u16,
},
} }
}) })
} }
@ -343,64 +307,43 @@ impl Tab {
direction: SplitDirection, direction: SplitDirection,
pane: Rc<dyn Pane>, pane: Rc<dyn Pane>,
) -> anyhow::Result<usize> { ) -> anyhow::Result<usize> {
let new_size = self let split_info = self
.compute_split_size(pane_index, direction) .compute_split_size(pane_index, direction)
.ok_or_else(|| anyhow::anyhow!("invalid pane_index {}; cannot split!", pane_index))?; .ok_or_else(|| anyhow::anyhow!("invalid pane_index {}; cannot split!", pane_index))?;
pane.resize(new_size.clone())?; let mut root = self.pane.borrow_mut();
let new_pane = Box::new(PaneNode::Single(pane)); let mut cursor = root.take().unwrap().cursor();
if let Some(root_node) = self.pane.borrow_mut().as_mut() { match cursor.go_to_nth_leaf(pane_index) {
let mut active = 0; Ok(c) => cursor = c,
let node = root_node Err(c) => {
.node_by_index_mut(pane_index, &mut active) root.replace(c.tree());
.ok_or_else(|| { anyhow::bail!("invalid pane_index {}; cannot split!", pane_index);
anyhow::anyhow!("invalid pane_index {}; cannot split!", pane_index)
})?;
let prior_node = match node {
PaneNode::Single(orig_pane) => {
orig_pane.resize(new_size)?;
Box::new(PaneNode::Single(Rc::clone(orig_pane)))
}
_ => unreachable!("impossible PaneNode variant returned from node_by_index_mut"),
};
match direction {
SplitDirection::Horizontal => {
*node = PaneNode::HorizontalSplit {
left: prior_node,
right: new_pane,
left_width: new_size.cols as _,
}
}
SplitDirection::Vertical => {
*node = PaneNode::VerticalSplit {
top: prior_node,
bottom: new_pane,
top_height: new_size.rows as _,
}
}
} }
};
let new_index = pane_index + 1; pane.resize(split_info.second.clone())?;
*self.active.borrow_mut() = new_index; match cursor.split_leaf_and_insert_right(pane) {
Ok(c) => cursor = c,
Err(c) => {
root.replace(c.tree());
anyhow::bail!("invalid pane_index {}; cannot split!", pane_index);
}
};
Ok(new_index) // cursor now points to the newly created split node;
} else { // we need to populate its split information
anyhow::bail!("no panes have been assigned; cannot split!"); match cursor.assign_node(Some(split_info)) {
} Err(c) | Ok(c) => root.replace(c.tree()),
};
*self.active.borrow_mut() = pane_index + 1;
Ok(pane_index + 1)
} }
} }
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum SplitDirection {
Horizontal,
Vertical,
}
/// A Pane represents a view on a terminal /// A Pane represents a view on a terminal
#[async_trait(?Send)] #[async_trait(?Send)]
pub trait Pane: Downcast { pub trait Pane: Downcast {
@ -558,27 +501,53 @@ mod test {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
horz_size, horz_size,
PtySize { SplitDirectionAndSize {
rows: 24, direction: SplitDirection::Horizontal,
cols: 40, left: 0,
pixel_width: 400, top: 0,
pixel_height: 600 first: PtySize {
rows: 24,
cols: 40,
pixel_width: 400,
pixel_height: 600
},
second: PtySize {
rows: 24,
cols: 39,
pixel_width: 390,
pixel_height: 600
},
} }
); );
let vert_size = tab.compute_split_size(0, SplitDirection::Vertical).unwrap(); let vert_size = tab.compute_split_size(0, SplitDirection::Vertical).unwrap();
assert_eq!( assert_eq!(
vert_size, vert_size,
PtySize { SplitDirectionAndSize {
rows: 12, direction: SplitDirection::Vertical,
cols: 80, left: 0,
pixel_width: 800, top: 0,
pixel_height: 300 first: PtySize {
rows: 12,
cols: 80,
pixel_width: 800,
pixel_height: 300
},
second: PtySize {
rows: 11,
cols: 80,
pixel_width: 800,
pixel_height: 275
}
} }
); );
let new_index = tab let new_index = tab
.split_and_insert(0, SplitDirection::Horizontal, FakePane::new(2, horz_size)) .split_and_insert(
0,
SplitDirection::Horizontal,
FakePane::new(2, horz_size.second),
)
.unwrap(); .unwrap();
assert_eq!(new_index, 1); assert_eq!(new_index, 1);
@ -595,15 +564,19 @@ mod test {
assert_eq!(1, panes[1].index); assert_eq!(1, panes[1].index);
assert_eq!(true, panes[1].is_active); assert_eq!(true, panes[1].is_active);
assert_eq!(40, panes[1].left); assert_eq!(41, panes[1].left);
assert_eq!(0, panes[1].top); assert_eq!(0, panes[1].top);
assert_eq!(40, panes[1].width); assert_eq!(39, panes[1].width);
assert_eq!(24, panes[1].height); assert_eq!(24, panes[1].height);
assert_eq!(2, panes[1].pane.pane_id()); assert_eq!(2, panes[1].pane.pane_id());
let vert_size = tab.compute_split_size(0, SplitDirection::Vertical).unwrap(); let vert_size = tab.compute_split_size(0, SplitDirection::Vertical).unwrap();
let new_index = tab let new_index = tab
.split_and_insert(0, SplitDirection::Vertical, FakePane::new(3, vert_size)) .split_and_insert(
0,
SplitDirection::Vertical,
FakePane::new(3, vert_size.second),
)
.unwrap(); .unwrap();
assert_eq!(new_index, 1); assert_eq!(new_index, 1);
@ -621,16 +594,16 @@ mod test {
assert_eq!(1, panes[1].index); assert_eq!(1, panes[1].index);
assert_eq!(true, panes[1].is_active); assert_eq!(true, panes[1].is_active);
assert_eq!(0, panes[1].left); assert_eq!(0, panes[1].left);
assert_eq!(12, panes[1].top); assert_eq!(13, panes[1].top);
assert_eq!(40, panes[1].width); assert_eq!(40, panes[1].width);
assert_eq!(12, panes[1].height); assert_eq!(11, panes[1].height);
assert_eq!(3, panes[1].pane.pane_id()); assert_eq!(3, panes[1].pane.pane_id());
assert_eq!(2, panes[2].index); assert_eq!(2, panes[2].index);
assert_eq!(false, panes[2].is_active); assert_eq!(false, panes[2].is_active);
assert_eq!(40, panes[2].left); assert_eq!(41, panes[2].left);
assert_eq!(0, panes[2].top); assert_eq!(0, panes[2].top);
assert_eq!(40, panes[2].width); assert_eq!(39, panes[2].width);
assert_eq!(24, panes[2].height); assert_eq!(24, panes[2].height);
assert_eq!(2, panes[2].pane.pane_id()); assert_eq!(2, panes[2].pane.pane_id());
} }