From 64059fba4759a8c05996c79db2a3b21cf44ccf7e Mon Sep 17 00:00:00 2001 From: wongjiahau Date: Sun, 19 Feb 2023 18:24:26 +0800 Subject: [PATCH] feat(tree): move left/right --- helix-term/src/ui/explore.rs | 46 +++--- helix-term/src/ui/tree.rs | 293 +++++++++++++++++++++++++++++------ 2 files changed, 273 insertions(+), 66 deletions(-) diff --git a/helix-term/src/ui/explore.rs b/helix-term/src/ui/explore.rs index b7691522..b887c5f2 100644 --- a/helix-term/src/ui/explore.rs +++ b/helix-term/src/ui/explore.rs @@ -281,26 +281,36 @@ impl Explorer { let body_area = area.clip_top(2); let style = editor.theme.get("ui.text"); let content = if self.show_help { - vec![ - "? Toggle help", - "a Add file", - "A Add folder", - "r Rename file/folder", - "d Delete file", - "/ Search", - "f Filter", - "[ Change root to parent folder", - "] Change root to current folder", - "^o Go to previous root", - "R Refresh", - "+ Increase size", - "- Decrease size", - "q Close", + let instructions = vec![ + ("?", "Toggle help"), + ("a", "Add file"), + ("A", "Add folder"), + ("r", "Rename file/folder"), + ("d", "Delete file"), + ("/", "Search"), + ("f", "Filter"), + ("[", "Change root to parent folder"), + ("]", "Change root to current folder"), + ("^o", "Go to previous root"), + ("R", "Refresh"), + ("+", "Increase size"), + ("-", "Decrease size"), + ("q", "Close"), ] .into_iter() - .map(|s| s.to_string()) - .chain(ui::tree::tree_view_help()) - .collect() + .chain(ui::tree::tree_view_help().into_iter()) + .collect::>(); + let max_left_length = instructions + .iter() + .map(|(key, _)| key.chars().count()) + .max() + .unwrap_or(0); + instructions + .into_iter() + .map(|(key, description)| { + format!("{:width$}{}", key, description, width = max_left_length + 1) + }) + .collect::>() } else { get_preview(&item.path, body_area.height as usize) .unwrap_or_else(|err| vec![err.to_string()]) diff --git a/helix-term/src/ui/tree.rs b/helix-term/src/ui/tree.rs index 8dcb197f..0aaabf0b 100644 --- a/helix-term/src/ui/tree.rs +++ b/helix-term/src/ui/tree.rs @@ -287,17 +287,22 @@ impl Tree { pub struct TreeView { tree: Tree, + /// Selected item idex selected: usize, /// (selected, row) save_view: (usize, usize), - /// View row + /// For implementing vertical scroll winline: usize, previous_area: Rect, + + /// For implementing horizontal scoll column: usize, + + /// For implementing horizontal scoll max_len: usize, count: usize, tree_symbol_style: String, @@ -424,14 +429,14 @@ impl TreeView { self.tree.regenerate_index(); } - fn go_to_parent(&mut self) { + fn move_to_parent(&mut self) { if let Some(parent) = self.current_parent() { let index = parent.index; self.set_selected(index) } } - fn go_to_children(&mut self, filter: &String) -> Result<()> { + fn move_to_children(&mut self, filter: &String) -> Result<()> { let current = self.current_mut(); if current.is_opened { self.set_selected(self.selected + 1); @@ -450,38 +455,45 @@ impl TreeView { self.tree.refresh(filter) } - fn go_to_first(&mut self) { + fn move_to_first(&mut self) { self.move_up(usize::MAX / 2) } - fn go_to_last(&mut self) { + fn move_to_last(&mut self) { self.move_down(usize::MAX / 2) } fn set_previous_area(&mut self, area: Rect) { self.previous_area = area } + + fn move_leftmost(&mut self) { + self.move_left(usize::MAX / 2); + } + + fn move_rightmost(&mut self) { + self.move_right(usize::MAX / 2) + } } -pub fn tree_view_help() -> Vec { +pub fn tree_view_help() -> Vec<(&'static str, &'static str)> { vec![ - "j/↓ Down", - "k/↑ Up", - "h/← Go to parent", - "l/→ Expand", - "L Scroll right", - "H Scroll left", - "zz Align view center", - "zt Align view top", - "zb Align view bottom", - "gg Go to first", - "ge Go to last", - "^d Page down", - "^u Page up", + ("j, down", "Down"), + ("k, up", "Up"), + ("h, left", "Go to parent"), + ("l, right", "Expand"), + ("L", "Scroll right"), + ("H", "Scroll left"), + ("zz", "Align view center"), + ("zt", "Align view top"), + ("zb", "Align view bottom"), + ("gg", "Go to first line"), + ("ge", "Go to last line"), + ("gh", "Go to line start"), + ("gl", "Go to line end"), + ("C-d", "Page down"), + ("C-u", "Page up"), ] - .into_iter() - .map(|s| s.to_string()) - .collect() } impl TreeView { @@ -577,7 +589,8 @@ impl TreeView { pub fn move_right(&mut self, cols: usize) { let max_scroll = self .max_len - .saturating_sub(self.previous_area.width as usize); + .saturating_sub(self.previous_area.width as usize) + .saturating_add(1); self.column = max_scroll.min(self.column + cols); } @@ -691,8 +704,6 @@ struct RenderTreeParams<'a, T> { level: usize, selected: usize, filter: &'a str, - column_start: usize, - max_width: usize, } fn render_tree( @@ -702,8 +713,6 @@ fn render_tree( level, selected, filter, - column_start, - max_width, }: RenderTreeParams, ) -> Vec { let indent = if level > 0 { @@ -720,15 +729,12 @@ fn render_tree( } else { "".to_string() }; - let indent = indent[column_start..].to_string(); - let indent_len = indent.len(); let name = tree.item.name(); - println!("{max_width}"); let head = RenderedLine { indent, selected: selected == tree.index, descendant_selected: selected != tree.index && tree.get(selected).is_some(), - name: name[..(max_width - indent_len).clamp(0, name.len())].to_string(), + name, }; let prefix = format!("{}{}", prefix, if level == 0 { "" } else { " " }); vec![head] @@ -740,8 +746,6 @@ fn render_tree( level: level + 1, selected, filter, - column_start, - max_width, }) })) .collect() @@ -749,7 +753,6 @@ fn render_tree( impl TreeView { pub fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context, filter: &String) { - self.max_len = 0; let style = cx.editor.theme.get(&self.tree_symbol_style); let ancestor_style = cx.editor.theme.get("ui.text.focus"); @@ -820,14 +823,49 @@ impl TreeView { level: 0, selected: self.selected, filter, - column_start: self.column, - max_width: self.previous_area.width as usize, }; let lines = render_tree(params); + self.max_len = lines + .iter() + .map(|line| { + line.indent + .chars() + .count() + .saturating_add(line.name.chars().count()) + }) + .max() + .unwrap_or(0); + + let max_width = self.previous_area.width as usize; + lines .into_iter() + // Horizontal scroll + .map(|line| { + let skip = self.column; + let indent_len = line.indent.chars().count(); + RenderedLine { + indent: if line.indent.is_empty() { + "".to_string() + } else { + line.indent + .chars() + .skip(skip) + .take(max_width) + .collect::() + }, + name: line + .name + .chars() + .skip(skip.saturating_sub(indent_len)) + .take((max_width.saturating_sub(indent_len)).clamp(0, line.name.len())) + .collect::(), + ..line + } + }) + // Vertical scroll .skip(skip) .take(area.height as usize) .collect() @@ -862,8 +900,8 @@ impl TreeView { _ => {} })); } - key!('h') | key!(Left) => self.go_to_parent(), - key!('l') | key!(Right) => match self.go_to_children(filter) { + key!('h') | key!(Left) => self.move_to_parent(), + key!('l') | key!(Right) => match self.move_to_children(filter) { Ok(_) => {} Err(err) => cx.editor.set_error(err.to_string()), }, @@ -874,8 +912,10 @@ impl TreeView { ctrl!('u') => self.move_up_half_page(), key!('g') => { self.on_next_key = Some(Box::new(|_, tree, event| match event { - key!('g') => tree.go_to_first(), - key!('e') => tree.go_to_last(), + key!('g') => tree.move_to_first(), + key!('e') => tree.move_to_last(), + key!('h') => tree.move_leftmost(), + key!('l') => tree.move_rightmost(), _ => {} })); } @@ -1141,10 +1181,10 @@ mod test_tree_view { } #[test] - fn test_go_to_first_last() { + fn test_move_to_first_last() { let mut view = dummy_tree_view(); - view.go_to_last(); + view.move_to_last(); assert_eq!( render(&mut view), " @@ -1157,7 +1197,7 @@ mod test_tree_view { .trim() ); - view.go_to_first(); + view.move_to_first(); assert_eq!( render(&mut view), " @@ -1255,11 +1295,11 @@ mod test_tree_view { } #[test] - fn go_to_children_parent() { + fn move_to_children_parent() { let filter = "".to_string(); let mut view = dummy_tree_view(); view.move_down(1); - view.go_to_children(&filter).unwrap(); + view.move_to_children(&filter).unwrap(); assert_eq!( render(&mut view), " @@ -1285,7 +1325,7 @@ mod test_tree_view { .trim() ); - view.go_to_parent(); + view.move_to_parent(); assert_eq!( render(&mut view), " @@ -1298,8 +1338,8 @@ mod test_tree_view { .trim() ); - view.go_to_last(); - view.go_to_parent(); + view.move_to_last(); + view.move_to_parent(); assert_eq!( render(&mut view), " @@ -1334,6 +1374,163 @@ mod test_tree_view { assert_eq!( render(&mut view), " +(ho_lives_in_a_pineap) + gary_the_snail + karen + king_neptune + krabby_patty +" + .trim() + ); + + view.move_right(1); + assert_eq!( + render(&mut view), + " +(o_lives_in_a_pineapp) +gary_the_snail +karen +king_neptune +krabby_patty +" + .trim() + ); + + view.move_right(1); + assert_eq!( + render(&mut view), + " +(_lives_in_a_pineappl) +ary_the_snail +aren +ing_neptune +rabby_patty +" + .trim() + ); + + view.move_left(1); + assert_eq!( + render(&mut view), + " +(o_lives_in_a_pineapp) +gary_the_snail +karen +king_neptune +krabby_patty +" + .trim() + ); + + view.move_leftmost(); + assert_eq!( + render(&mut view), + " +(who_lives_in_a_pinea) + gary_the_snail + karen + king_neptune + krabby_patty +" + .trim() + ); + + view.move_left(1); + assert_eq!( + render(&mut view), + " +(who_lives_in_a_pinea) + gary_the_snail + karen + king_neptune + krabby_patty +" + .trim() + ); + + view.move_rightmost(); + assert_eq!(render(&mut view), "(apple_under_the_sea)\n\n\n\n"); + } + + #[test] + fn test_move_to_parent_child() { + let mut view = dummy_tree_view(); + let filter = "".to_string(); + + view.move_to_children(&filter).unwrap(); + assert_eq!( + render(&mut view), + " +[who_lives_in_a_pineapple_under_the_sea] + (gary_the_snail) + karen + king_neptune + krabby_patty +" + .trim() + ); + + view.move_to_children(&filter).unwrap(); + assert_eq!( + render(&mut view), + " +[who_lives_in_a_pineapple_under_the_sea] + [gary_the_snail] +  (e_snail) +  gary_th + karen +" + .trim() + ); + + view.move_down(1); + assert_eq!( + render(&mut view), + " +[who_lives_in_a_pineapple_under_the_sea] + [gary_the_snail] +  e_snail +  (gary_th) + karen +" + .trim() + ); + + view.move_to_parent(); + assert_eq!( + render(&mut view), + " +[who_lives_in_a_pineapple_under_the_sea] + (gary_the_snail) +  e_snail +  gary_th + karen +" + .trim() + ); + + view.move_to_parent(); + assert_eq!( + render(&mut view), + " +(who_lives_in_a_pineapple_under_the_sea) + gary_the_snail +  e_snail +  gary_th + karen +" + .trim() + ); + + view.move_to_parent(); + assert_eq!( + render(&mut view), + " +(who_lives_in_a_pineapple_under_the_sea) + gary_the_snail +  e_snail +  gary_th + karen " .trim() )