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.rs | 623 |
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<_>>() - ); - } -} |