Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-view/src/tree.rs')
-rw-r--r--helix-view/src/tree.rs623
1 files changed, 64 insertions, 559 deletions
diff --git a/helix-view/src/tree.rs b/helix-view/src/tree.rs
index d596b35a..576f64f0 100644
--- a/helix-view/src/tree.rs
+++ b/helix-view/src/tree.rs
@@ -45,21 +45,15 @@ impl Node {
}
}
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+// TODO: screen coord to container + container coordinate helpers
+
+#[derive(Debug, PartialEq, Eq)]
pub enum Layout {
Horizontal,
Vertical,
// could explore stacked/tabbed
}
-#[derive(Debug, Clone, Copy)]
-pub enum Direction {
- Up,
- Down,
- Left,
- Right,
-}
-
#[derive(Debug)]
pub struct Container {
layout: Layout,
@@ -156,6 +150,7 @@ impl Tree {
} => container,
_ => unreachable!(),
};
+
if container.layout == layout {
// insert node after the current item if there is children already
let pos = if container.children.is_empty() {
@@ -214,56 +209,34 @@ impl Tree {
node
}
- /// Get a mutable reference to a [Container] by index.
- /// # Panics
- /// Panics if `index` is not in self.nodes, or if the node's content is not a [Content::Container].
- fn container_mut(&mut self, index: ViewId) -> &mut Container {
- match &mut self.nodes[index] {
- Node {
- content: Content::Container(container),
- ..
- } => container,
- _ => unreachable!(),
- }
- }
-
- fn remove_or_replace(&mut self, child: ViewId, replacement: Option<ViewId>) {
- let parent = self.nodes[child].parent;
-
- self.nodes.remove(child);
-
- let container = self.container_mut(parent);
- let pos = container
- .children
- .iter()
- .position(|&item| item == child)
- .unwrap();
-
- if let Some(new) = replacement {
- container.children[pos] = new;
- self.nodes[new].parent = parent;
- } else {
- container.children.remove(pos);
- }
- }
-
pub fn remove(&mut self, index: ViewId) {
+ let mut stack = Vec::new();
+
if self.focus == index {
// focus on something else
- self.focus = self.prev();
+ self.focus_next();
}
- let parent = self.nodes[index].parent;
- let parent_is_root = parent == self.root;
-
- self.remove_or_replace(index, None);
+ stack.push(index);
- let parent_container = self.container_mut(parent);
- if parent_container.children.len() == 1 && !parent_is_root {
- // Lets merge the only child back to its grandparent so that Views
- // are equally spaced.
- let sibling = parent_container.children.pop().unwrap();
- self.remove_or_replace(parent, Some(sibling));
+ while let Some(index) = stack.pop() {
+ let parent_id = self.nodes[index].parent;
+ if let Node {
+ content: Content::Container(container),
+ ..
+ } = &mut self.nodes[parent_id]
+ {
+ if let Some(pos) = container.children.iter().position(|&child| child == index) {
+ container.children.remove(pos);
+
+ // TODO: if container now only has one child, remove it and place child in parent
+ if container.children.is_empty() && parent_id != self.root {
+ // if container now empty, remove it
+ stack.push(parent_id);
+ }
+ }
+ }
+ self.nodes.remove(index);
}
self.recalculate()
@@ -293,31 +266,16 @@ impl Tree {
})
}
- /// Get reference to a [View] by index.
- /// # Panics
- ///
- /// Panics if `index` is not in self.nodes, or if the node's content is not [Content::View]. This can be checked with [Self::contains].
pub fn get(&self, index: ViewId) -> &View {
- self.try_get(index).unwrap()
- }
-
- /// Try to get reference to a [View] by index. Returns `None` if node content is not a [`Content::View`].
- ///
- /// Does not panic if the view does not exists anymore.
- pub fn try_get(&self, index: ViewId) -> Option<&View> {
- match self.nodes.get(index) {
- Some(Node {
+ match &self.nodes[index] {
+ Node {
content: Content::View(view),
..
- }) => Some(view),
- _ => None,
+ } => view,
+ _ => unreachable!(),
}
}
- /// Get a mutable reference to a [View] by index.
- /// # Panics
- ///
- /// Panics if `index` is not in self.nodes, or if the node's content is not [Content::View]. This can be checked with [Self::contains].
pub fn get_mut(&mut self, index: ViewId) -> &mut View {
match &mut self.nodes[index] {
Node {
@@ -328,11 +286,6 @@ impl Tree {
}
}
- /// Check if tree contains a [Node] with a given index.
- pub fn contains(&self, index: ViewId) -> bool {
- self.nodes.contains_key(index)
- }
-
pub fn is_empty(&self) -> bool {
match &self.nodes[self.root] {
Node {
@@ -354,9 +307,6 @@ impl Tree {
pub fn recalculate(&mut self) {
if self.is_empty() {
- // There are no more views, so the tree should focus itself again.
- self.focus = self.root;
-
return;
}
@@ -407,13 +357,11 @@ impl Tree {
}
Layout::Vertical => {
let len = container.children.len();
- let len_u16 = len as u16;
- let inner_gap = 1u16;
- let total_gap = inner_gap * len_u16.saturating_sub(2);
+ let width = area.width / len as u16;
- let used_area = area.width.saturating_sub(total_gap);
- let width = used_area / len_u16;
+ let inner_gap = 1u16;
+ // let total_gap = inner_gap * (len as u16 - 1);
let mut child_x = area.x;
@@ -441,228 +389,48 @@ impl Tree {
}
}
- pub fn traverse(&self) -> Traverse<'_> {
+ pub fn traverse(&self) -> Traverse {
Traverse::new(self)
}
- // Finds the split in the given direction if it exists
- pub fn find_split_in_direction(&self, id: ViewId, direction: Direction) -> Option<ViewId> {
- let parent = self.nodes[id].parent;
- // Base case, we found the root of the tree
- if parent == id {
- return None;
- }
- // Parent must always be a container
- let parent_container = match &self.nodes[parent].content {
- Content::Container(container) => container,
- Content::View(_) => unreachable!(),
- };
-
- match (direction, parent_container.layout) {
- (Direction::Up, Layout::Vertical)
- | (Direction::Left, Layout::Horizontal)
- | (Direction::Right, Layout::Horizontal)
- | (Direction::Down, Layout::Vertical) => {
- // The desired direction of movement is not possible within
- // the parent container so the search must continue closer to
- // the root of the split tree.
- self.find_split_in_direction(parent, direction)
- }
- (Direction::Up, Layout::Horizontal)
- | (Direction::Down, Layout::Horizontal)
- | (Direction::Left, Layout::Vertical)
- | (Direction::Right, Layout::Vertical) => {
- // It's possible to move in the desired direction within
- // the parent container so an attempt is made to find the
- // correct child.
- match self.find_child(id, &parent_container.children, direction) {
- // Child is found, search is ended
- Some(id) => Some(id),
- // A child is not found. This could be because of either two scenarios
- // 1. Its not possible to move in the desired direction, and search should end
- // 2. A layout like the following with focus at X and desired direction Right
- // | _ | x | |
- // | _ _ _ | |
- // | _ _ _ | |
- // The container containing X ends at X so no rightward movement is possible
- // however there still exists another view/container to the right that hasn't
- // been explored. Thus another search is done here in the parent container
- // before concluding it's not possible to move in the desired direction.
- None => self.find_split_in_direction(parent, direction),
- }
- }
- }
- }
-
- fn find_child(&self, id: ViewId, children: &[ViewId], direction: Direction) -> Option<ViewId> {
- let mut child_id = match direction {
- // index wise in the child list the Up and Left represents a -1
- // thus reversed iterator.
- Direction::Up | Direction::Left => children
- .iter()
- .rev()
- .skip_while(|i| **i != id)
- .copied()
- .nth(1)?,
- // Down and Right => +1 index wise in the child list
- Direction::Down | Direction::Right => {
- children.iter().skip_while(|i| **i != id).copied().nth(1)?
- }
- };
- let (current_x, current_y) = match &self.nodes[self.focus].content {
- Content::View(current_view) => (current_view.area.left(), current_view.area.top()),
- Content::Container(_) => unreachable!(),
- };
-
- // If the child is a container the search finds the closest container child
- // visually based on screen location.
- while let Content::Container(container) = &self.nodes[child_id].content {
- match (direction, container.layout) {
- (_, Layout::Vertical) => {
- // find closest split based on x because y is irrelevant
- // in a vertical container (and already correct based on previous search)
- child_id = *container.children.iter().min_by_key(|id| {
- let x = match &self.nodes[**id].content {
- Content::View(view) => view.area.left(),
- Content::Container(container) => container.area.left(),
- };
- (current_x as i16 - x as i16).abs()
- })?;
- }
- (_, Layout::Horizontal) => {
- // find closest split based on y because x is irrelevant
- // in a horizontal container (and already correct based on previous search)
- child_id = *container.children.iter().min_by_key(|id| {
- let y = match &self.nodes[**id].content {
- Content::View(view) => view.area.top(),
- Content::Container(container) => container.area.top(),
- };
- (current_y as i16 - y as i16).abs()
- })?;
- }
- }
- }
- Some(child_id)
- }
-
- pub fn prev(&self) -> ViewId {
- // This function is very dumb, but that's because we don't store any parent links.
- // (we'd be able to go parent.prev_sibling() recursively until we find something)
- // For now that's okay though, since it's unlikely you'll be able to open a large enough
- // number of splits to notice.
-
- let mut views = self
- .traverse()
- .rev()
- .skip_while(|&(id, _view)| id != self.focus)
- .skip(1); // Skip focused value
- if let Some((id, _)) = views.next() {
- id
- } else {
- // extremely crude, take the last item
- let (key, _) = self.traverse().next_back().unwrap();
- key
- }
- }
-
- pub fn next(&self) -> ViewId {
+ pub fn focus_next(&mut self) {
// This function is very dumb, but that's because we don't store any parent links.
// (we'd be able to go parent.next_sibling() recursively until we find something)
// For now that's okay though, since it's unlikely you'll be able to open a large enough
// number of splits to notice.
- let mut views = self
- .traverse()
- .skip_while(|&(id, _view)| id != self.focus)
- .skip(1); // Skip focused value
- if let Some((id, _)) = views.next() {
- id
+ // current = focus
+ // let found = loop do {
+ // node = focus.parent;
+ // let found = node.next_sibling_of(current)
+ // if some {
+ // break found;
+ // }
+ // // else
+ // if node == root {
+ // return first child of root;
+ // };
+ // current = parent;
+ // }
+ // }
+ //
+ // use found next sibling
+ // loop do {
+ // if found = view -> focus = found, return
+ // if found = container -> found = first child
+ // }
+
+ let iter = self.traverse();
+
+ let mut iter = iter.skip_while(|&(key, _view)| key != self.focus);
+ iter.next(); // take the focused value
+
+ if let Some((key, _)) = iter.next() {
+ self.focus = key;
} else {
// extremely crude, take the first item again
let (key, _) = self.traverse().next().unwrap();
- key
- }
- }
-
- pub fn transpose(&mut self) {
- let focus = self.focus;
- let parent = self.nodes[focus].parent;
- if let Content::Container(container) = &mut self.nodes[parent].content {
- container.layout = match container.layout {
- Layout::Vertical => Layout::Horizontal,
- Layout::Horizontal => Layout::Vertical,
- };
- self.recalculate();
- }
- }
-
- pub fn swap_split_in_direction(&mut self, direction: Direction) -> Option<()> {
- let focus = self.focus;
- let target = self.find_split_in_direction(focus, direction)?;
- let focus_parent = self.nodes[focus].parent;
- let target_parent = self.nodes[target].parent;
-
- if focus_parent == target_parent {
- let parent = focus_parent;
- let [parent, focus, target] = self.nodes.get_disjoint_mut([parent, focus, target])?;
- match (&mut parent.content, &mut focus.content, &mut target.content) {
- (
- Content::Container(parent),
- Content::View(focus_view),
- Content::View(target_view),
- ) => {
- let focus_pos = parent.children.iter().position(|id| focus_view.id == *id)?;
- let target_pos = parent
- .children
- .iter()
- .position(|id| target_view.id == *id)?;
- // swap node positions so that traversal order is kept
- parent.children[focus_pos] = target_view.id;
- parent.children[target_pos] = focus_view.id;
- // swap area so that views rendered at the correct location
- std::mem::swap(&mut focus_view.area, &mut target_view.area);
-
- Some(())
- }
- _ => unreachable!(),
- }
- } else {
- let [focus_parent, target_parent, focus, target] =
- self.nodes
- .get_disjoint_mut([focus_parent, target_parent, focus, target])?;
- match (
- &mut focus_parent.content,
- &mut target_parent.content,
- &mut focus.content,
- &mut target.content,
- ) {
- (
- Content::Container(focus_parent),
- Content::Container(target_parent),
- Content::View(focus_view),
- Content::View(target_view),
- ) => {
- let focus_pos = focus_parent
- .children
- .iter()
- .position(|id| focus_view.id == *id)?;
- let target_pos = target_parent
- .children
- .iter()
- .position(|id| target_view.id == *id)?;
- // re-parent target and focus nodes
- std::mem::swap(
- &mut focus_parent.children[focus_pos],
- &mut target_parent.children[target_pos],
- );
- std::mem::swap(&mut focus.parent, &mut target.parent);
- // swap area so that views rendered at the correct location
- std::mem::swap(&mut focus_view.area, &mut target_view.area);
-
- Some(())
- }
- _ => unreachable!(),
- }
+ self.focus = key;
}
}
@@ -704,266 +472,3 @@ impl<'a> Iterator for Traverse<'a> {
}
}
}
-
-impl DoubleEndedIterator for Traverse<'_> {
- fn next_back(&mut self) -> Option<Self::Item> {
- loop {
- let key = self.stack.pop()?;
-
- let node = &self.tree.nodes[key];
-
- match &node.content {
- Content::View(view) => return Some((key, view)),
- Content::Container(container) => {
- self.stack.extend(container.children.iter());
- }
- }
- }
- }
-}
-
-#[cfg(test)]
-mod test {
- use super::*;
- use crate::editor::GutterConfig;
- use crate::DocumentId;
-
- #[test]
- fn find_split_in_direction() {
- let mut tree = Tree::new(Rect {
- x: 0,
- y: 0,
- width: 180,
- height: 80,
- });
- let mut view = View::new(DocumentId::default(), GutterConfig::default());
- view.area = Rect::new(0, 0, 180, 80);
- tree.insert(view);
-
- let l0 = tree.focus;
- let view = View::new(DocumentId::default(), GutterConfig::default());
- tree.split(view, Layout::Vertical);
- let r0 = tree.focus;
-
- tree.focus = l0;
- let view = View::new(DocumentId::default(), GutterConfig::default());
- tree.split(view, Layout::Horizontal);
- let l1 = tree.focus;
-
- tree.focus = l0;
- let view = View::new(DocumentId::default(), GutterConfig::default());
- tree.split(view, Layout::Vertical);
-
- // Tree in test
- // | L0 | L2 | |
- // | L1 | R0 |
- let l2 = tree.focus;
- assert_eq!(Some(l0), tree.find_split_in_direction(l2, Direction::Left));
- assert_eq!(Some(l1), tree.find_split_in_direction(l2, Direction::Down));
- assert_eq!(Some(r0), tree.find_split_in_direction(l2, Direction::Right));
- assert_eq!(None, tree.find_split_in_direction(l2, Direction::Up));
-
- tree.focus = l1;
- assert_eq!(None, tree.find_split_in_direction(l1, Direction::Left));
- assert_eq!(None, tree.find_split_in_direction(l1, Direction::Down));
- assert_eq!(Some(r0), tree.find_split_in_direction(l1, Direction::Right));
- assert_eq!(Some(l0), tree.find_split_in_direction(l1, Direction::Up));
-
- tree.focus = l0;
- assert_eq!(None, tree.find_split_in_direction(l0, Direction::Left));
- assert_eq!(Some(l1), tree.find_split_in_direction(l0, Direction::Down));
- assert_eq!(Some(l2), tree.find_split_in_direction(l0, Direction::Right));
- assert_eq!(None, tree.find_split_in_direction(l0, Direction::Up));
-
- tree.focus = r0;
- assert_eq!(Some(l2), tree.find_split_in_direction(r0, Direction::Left));
- assert_eq!(None, tree.find_split_in_direction(r0, Direction::Down));
- assert_eq!(None, tree.find_split_in_direction(r0, Direction::Right));
- assert_eq!(None, tree.find_split_in_direction(r0, Direction::Up));
- }
-
- #[test]
- fn swap_split_in_direction() {
- let mut tree = Tree::new(Rect {
- x: 0,
- y: 0,
- width: 180,
- height: 80,
- });
-
- let doc_l0 = DocumentId::default();
- let mut view = View::new(doc_l0, GutterConfig::default());
- view.area = Rect::new(0, 0, 180, 80);
- tree.insert(view);
-
- let l0 = tree.focus;
-
- let doc_r0 = DocumentId::default();
- let view = View::new(doc_r0, GutterConfig::default());
- tree.split(view, Layout::Vertical);
- let r0 = tree.focus;
-
- tree.focus = l0;
-
- let doc_l1 = DocumentId::default();
- let view = View::new(doc_l1, GutterConfig::default());
- tree.split(view, Layout::Horizontal);
- let l1 = tree.focus;
-
- tree.focus = l0;
-
- let doc_l2 = DocumentId::default();
- let view = View::new(doc_l2, GutterConfig::default());
- tree.split(view, Layout::Vertical);
- let l2 = tree.focus;
-
- // Views in test
- // | L0 | L2 | |
- // | L1 | R0 |
-
- // Document IDs in test
- // | l0 | l2 | |
- // | l1 | r0 |
-
- fn doc_id(tree: &Tree, view_id: ViewId) -> Option<DocumentId> {
- if let Content::View(view) = &tree.nodes[view_id].content {
- Some(view.doc)
- } else {
- None
- }
- }
-
- tree.focus = l0;
- // `*` marks the view in focus from view table (here L0)
- // | l0* | l2 | |
- // | l1 | r0 |
- tree.swap_split_in_direction(Direction::Down);
- // | l1 | l2 | |
- // | l0* | r0 |
- assert_eq!(tree.focus, l0);
- assert_eq!(doc_id(&tree, l0), Some(doc_l1));
- assert_eq!(doc_id(&tree, l1), Some(doc_l0));
- assert_eq!(doc_id(&tree, l2), Some(doc_l2));
- assert_eq!(doc_id(&tree, r0), Some(doc_r0));
-
- tree.swap_split_in_direction(Direction::Right);
-
- // | l1 | l2 | |
- // | r0 | l0* |
- assert_eq!(tree.focus, l0);
- assert_eq!(doc_id(&tree, l0), Some(doc_l1));
- assert_eq!(doc_id(&tree, l1), Some(doc_r0));
- assert_eq!(doc_id(&tree, l2), Some(doc_l2));
- assert_eq!(doc_id(&tree, r0), Some(doc_l0));
-
- // cannot swap, nothing changes
- tree.swap_split_in_direction(Direction::Up);
- // | l1 | l2 | |
- // | r0 | l0* |
- assert_eq!(tree.focus, l0);
- assert_eq!(doc_id(&tree, l0), Some(doc_l1));
- assert_eq!(doc_id(&tree, l1), Some(doc_r0));
- assert_eq!(doc_id(&tree, l2), Some(doc_l2));
- assert_eq!(doc_id(&tree, r0), Some(doc_l0));
-
- // cannot swap, nothing changes
- tree.swap_split_in_direction(Direction::Down);
- // | l1 | l2 | |
- // | r0 | l0* |
- assert_eq!(tree.focus, l0);
- assert_eq!(doc_id(&tree, l0), Some(doc_l1));
- assert_eq!(doc_id(&tree, l1), Some(doc_r0));
- assert_eq!(doc_id(&tree, l2), Some(doc_l2));
- assert_eq!(doc_id(&tree, r0), Some(doc_l0));
-
- tree.focus = l2;
- // | l1 | l2* | |
- // | r0 | l0 |
-
- tree.swap_split_in_direction(Direction::Down);
- // | l1 | r0 | |
- // | l2* | l0 |
- assert_eq!(tree.focus, l2);
- assert_eq!(doc_id(&tree, l0), Some(doc_l1));
- assert_eq!(doc_id(&tree, l1), Some(doc_l2));
- assert_eq!(doc_id(&tree, l2), Some(doc_r0));
- assert_eq!(doc_id(&tree, r0), Some(doc_l0));
-
- tree.swap_split_in_direction(Direction::Up);
- // | l2* | r0 | |
- // | l1 | l0 |
- assert_eq!(tree.focus, l2);
- assert_eq!(doc_id(&tree, l0), Some(doc_l2));
- assert_eq!(doc_id(&tree, l1), Some(doc_l1));
- assert_eq!(doc_id(&tree, l2), Some(doc_r0));
- assert_eq!(doc_id(&tree, r0), Some(doc_l0));
- }
-
- #[test]
- fn all_vertical_views_have_same_width() {
- let tree_area_width = 180;
- let mut tree = Tree::new(Rect {
- x: 0,
- y: 0,
- width: tree_area_width,
- height: 80,
- });
- let mut view = View::new(DocumentId::default(), GutterConfig::default());
- view.area = Rect::new(0, 0, 180, 80);
- tree.insert(view);
-
- let view = View::new(DocumentId::default(), GutterConfig::default());
- tree.split(view, Layout::Vertical);
-
- let view = View::new(DocumentId::default(), GutterConfig::default());
- tree.split(view, Layout::Horizontal);
-
- tree.remove(tree.focus);
-
- let view = View::new(DocumentId::default(), GutterConfig::default());
- tree.split(view, Layout::Vertical);
-
- // Make sure that we only have one level in the tree.
- assert_eq!(3, tree.views().count());
- assert_eq!(
- vec![
- tree_area_width / 3 - 1, // gap here
- tree_area_width / 3 - 1, // gap here
- tree_area_width / 3
- ],
- tree.views()
- .map(|(view, _)| view.area.width)
- .collect::<Vec<_>>()
- );
- }
-
- #[test]
- fn vsplit_gap_rounding() {
- let (tree_area_width, tree_area_height) = (80, 24);
- let mut tree = Tree::new(Rect {
- x: 0,
- y: 0,
- width: tree_area_width,
- height: tree_area_height,
- });
- let mut view = View::new(DocumentId::default(), GutterConfig::default());
- view.area = Rect::new(0, 0, tree_area_width, tree_area_height);
- tree.insert(view);
-
- for _ in 0..9 {
- let view = View::new(DocumentId::default(), GutterConfig::default());
- tree.split(view, Layout::Vertical);
- }
-
- assert_eq!(10, tree.views().count());
- assert_eq!(
- std::iter::repeat(7)
- .take(9)
- .chain(Some(8)) // Rounding in `recalculate`.
- .collect::<Vec<_>>(),
- tree.views()
- .map(|(view, _)| view.area.width)
- .collect::<Vec<_>>()
- );
- }
-}