From aa397ef8013a73b1deb567bf4d41ca9f72714bcb Mon Sep 17 00:00:00 2001 From: wongjiahau Date: Sun, 12 Feb 2023 18:44:52 +0800 Subject: [PATCH] feat(explore): reveal current file --- helix-term/src/commands.rs | 8 +- helix-term/src/keymap/default.rs | 2 +- helix-term/src/ui/explore.rs | 54 +++++++---- helix-term/src/ui/tree.rs | 158 ++++++++++++++++++------------- 4 files changed, 131 insertions(+), 91 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index c7768094..ae725291 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -434,7 +434,7 @@ impl MappableCommand { replay_macro, "Replay macro", command_palette, "Open command pallete", toggle_or_focus_explorer, "Toggle or focus explorer", - focus_current_file, "Focus current file in explorer", + reveal_current_file, "Reveal current file in explorer", close_explorer, "close explorer", ); } @@ -2232,16 +2232,16 @@ fn toggle_or_focus_explorer(cx: &mut Context) { )); } -fn focus_current_file(cx: &mut Context) { +fn reveal_current_file(cx: &mut Context) { cx.callback = Some(Box::new( |compositor: &mut Compositor, cx: &mut compositor::Context| { if let Some(editor) = compositor.find::() { match editor.explorer.as_mut() { - Some(explore) => explore.content.focus_current_file(cx), + Some(explore) => explore.content.reveal_current_file(cx), None => match ui::Explorer::new(cx) { Ok(explore) => { let mut explorer = overlayed(explore); - explorer.content.focus_current_file(cx); + explorer.content.reveal_current_file(cx); editor.explorer = Some(explorer); } Err(err) => cx.editor.set_error(format!("{}", err)), diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index 167ef510..cc00e30d 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -265,7 +265,7 @@ pub fn default() -> HashMap { "h" => select_references_to_symbol_under_cursor, "?" => command_palette, "e" => toggle_or_focus_explorer, - "E" => focus_current_file, + "E" => reveal_current_file, }, "z" => { "View" "z" | "c" => align_view_center, diff --git a/helix-term/src/ui/explore.rs b/helix-term/src/ui/explore.rs index e1d8e06c..2e852e6e 100644 --- a/helix-term/src/ui/explore.rs +++ b/helix-term/src/ui/explore.rs @@ -277,13 +277,31 @@ impl Explorer { }) } - pub fn focus_current_file(&mut self, cx: &mut Context) { + pub fn reveal_current_file(&mut self, cx: &mut Context) { let current_document_path = doc!(cx.editor).path().cloned(); match current_document_path { None => cx.editor.set_error("No opened document."), - Some(path) => { - self.tree.focus_path(cx, path, &self.state.current_root); - self.focus(); + Some(current_path) => { + let current_root = &self.state.current_root; + let current_path = current_path.as_path().to_string_lossy().to_string(); + let current_root = current_root.as_path().to_string_lossy().to_string() + "/"; + let segments = current_path + .strip_prefix(current_root.as_str()) + .expect( + format!( + "Failed to strip prefix '{}' from '{}'", + current_root, current_path + ) + .as_str(), + ) + .split(std::path::MAIN_SEPARATOR) + .collect::>(); + match self.tree.reveal_item(segments) { + Ok(_) => { + self.focus(); + } + Err(error) => cx.editor.set_error(error), + } } } } @@ -798,20 +816,20 @@ impl Component for Explorer { self.repeat_motion = Some(repeat_motion); } } - key!('b') => { - if let Some(p) = self.state.current_root.parent() { - match Self::get_items(p.to_path_buf(), cx) { - Ok(items) => { - self.state.current_root = p.to_path_buf(); - let root = FileInfo::root(self.state.current_root.clone()); - let children = root.get_children().expect("TODO: handle error"); - self.tree = TreeView::build_tree(root, children) - .with_enter_fn(Self::toggle_current); - } - Err(e) => cx.editor.set_error(format!("{e}")), - } - } - } + // key!('b') => { + // if let Some(p) = self.state.current_root.parent() { + // match Self::get_items(p.to_path_buf(), cx) { + // Ok(items) => { + // self.state.current_root = p.to_path_buf(); + // let root = FileInfo::root(self.state.current_root.clone()); + // let children = root.get_children().expect("TODO: handle error"); + // self.tree = TreeView::build_tree(root, children) + // .with_enter_fn(Self::toggle_current); + // } + // Err(e) => cx.editor.set_error(format!("{e}")), + // } + // } + // } key!('f') => self.new_filter_prompt(), key!('/') => self.new_search_prompt(true), key!('?') => self.new_search_prompt(false), diff --git a/helix-term/src/ui/tree.rs b/helix-term/src/ui/tree.rs index 362ea43f..4fad6dcd 100644 --- a/helix-term/src/ui/tree.rs +++ b/helix-term/src/ui/tree.rs @@ -1,5 +1,3 @@ -use std::iter::Peekable; -use std::slice::Iter; use std::{cmp::Ordering, path::PathBuf}; use anyhow::Result; @@ -210,9 +208,16 @@ impl Tree { pub struct TreeView { tree: Tree, recycle: Option<(String, Vec>)>, - selected: usize, // select item index - save_view: (usize, usize), // (selected, row) - winline: usize, // view row + /// Selected item idex + selected: usize, + + /// (selected, row) + save_view: (usize, usize), + + /// View row + winline: usize, + + area_height: usize, col: usize, max_len: usize, count: usize, @@ -239,6 +244,7 @@ impl TreeView { col: 0, max_len: 0, count: 0, + area_height: 0, tree_symbol_style: "ui.text".into(), pre_render: None, on_opened_fn: None, @@ -292,66 +298,73 @@ impl TreeView { } } - /// TODO: current_path should not be PathBuf, but Vec so that Tree can be generic - pub fn focus_path(&mut self, cx: &mut Context, current_path: PathBuf, current_root: &PathBuf) { - let current_path = current_path.as_path().to_string_lossy().to_string(); - let current_root = current_root.as_path().to_string_lossy().to_string() + "/"; - let nodes = current_path - .strip_prefix(current_root.as_str()) - .expect( - format!( - "Failed to strip prefix '{}' from '{}'", - current_root, current_path - ) - .as_str(), - ) - .split(std::path::MAIN_SEPARATOR) - .enumerate() - .collect::>(); - - let len = nodes.len(); - - // `preivous_item_index` is necessary to avoid choosing the first file - // that is not the current file. - // For example, consider a project that contains multiple `Cargo.toml`. - // Without `previous_item_index`, the first `Cargo.toml` will always be chosen, - // regardless of which `Cargo.toml` the user wishes to find in the explorer. - // let mut previous_item_index = 0; - // for (index, node) in nodes { - // let current_level = index + 1; - // let is_last = index == len - 1; - // match self - // .items - // .iter() - // .enumerate() - // .position(|(item_index, item)| { - // item_index >= previous_item_index - // && item.item.text_string().eq(node) - // && item.level == current_level - // }) { - // Some(index) => { - // if is_last { - // self.selected = index - // } else { - // let item = &self.items[index]; - // let items = match item.item.get_childs() { - // Ok(items) => items, - // Err(e) => return cx.editor.set_error(format!("{e}")), - // }; - // let inserts = vec_to_tree(items, current_level + 1); - // previous_item_index = index; - // let _: Vec<_> = self.items.splice(index + 1..index + 1, inserts).collect(); - // } - // } - // None => cx.editor.set_error(format!( - // "The following file does not exist anymore: '{}'. node = {}", - // current_path, node - // )), - // } - // } + /// Reveal item in the tree based on the given `segments`. + /// + /// The name of the root should be excluded. + /// + /// Example `segments`: + /// ``` + /// vec!["helix-term", "src", "ui", "tree.rs"] + /// ``` + pub fn reveal_item(&mut self, segments: Vec<&str>) -> Result<(), String> { + // Expand the tree + segments.iter().fold( + Ok(&mut self.tree), + |current_tree, segment| match current_tree { + Err(err) => Err(err), + Ok(current_tree) => { + match current_tree + .children + .iter_mut() + .find(|tree| tree.item.text_string().eq(segment)) + { + Some(tree) => { + if !tree.is_opened { + tree.children = vec_to_tree( + tree.item.get_children().map_err(|err| err.to_string())?, + ); + if !tree.children.is_empty() { + tree.is_opened = true; + } + } + Ok(tree) + } + None => Err(format!( + "Unable to find path: '{}'. current_segment = {}", + segments.join("/"), + segment + )), + } + } + }, + )?; - // Center the selection - self.winline = self.max_len / 2; + // Locate the item + self.regenerate_index(); + self.selected = segments + .iter() + .fold(&self.tree, |tree, segment| { + tree.children + .iter() + .find(|tree| tree.item.text_string().eq(segment)) + .expect("Should be unreachable") + }) + .index; + + self.align_view_center(); + Ok(()) + } + + fn align_view_center(&mut self) { + self.winline = self.area_height / 2 + } + + fn align_view_top(&mut self) { + self.winline = 0 + } + + fn align_view_bottom(&mut self) { + self.winline = self.area_height } fn regenerate_index(&mut self) { @@ -626,11 +639,14 @@ impl TreeView { } self.max_len = 0; - self.winline = std::cmp::min(self.winline, area.height.saturating_sub(1) as usize); + self.area_height = area.height.saturating_sub(1) as usize; + self.winline = std::cmp::min(self.winline, self.area_height); let style = cx.editor.theme.get(&self.tree_symbol_style); let last_item_index = self.tree.len().saturating_sub(1); let skip = self.selected.saturating_sub(self.winline); + cx.editor.set_error(format!("winline = {}", self.winline)); + let params = RenderElemParams { tree: &self.tree, prefix: &"".to_string(), @@ -661,8 +677,6 @@ impl TreeView { selected: usize, } - cx.editor.set_error(format!("seleted = {}", self.selected)); - fn render_tree( RenderElemParams { tree, @@ -822,7 +836,15 @@ impl TreeView { key!(i @ '0'..='9') => self.count = i.to_digit(10).unwrap() as usize + count * 10, key!('k') | shift!(Tab) | key!(Up) | ctrl!('k') => self.move_up(1.max(count)), key!('j') | key!(Tab) | key!(Down) | ctrl!('j') => self.move_down(1.max(count)), - key!('z') => self.fold_current_child(), + key!('z') => { + self.on_next_key = Some(Box::new(|_, tree, event| match event.into() { + key!('f') => tree.fold_current_child(), + key!('z') => tree.align_view_center(), + key!('t') => tree.align_view_top(), + key!('b') => tree.align_view_bottom(), + _ => {} + })); + } key!('h') => self.go_to_parent(), key!('l') => self.go_to_children(cx), key!(Enter) => self.on_enter(cx, params, self.selected),