diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index a036407cdb9f..c77680942a74 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -433,8 +433,8 @@ impl MappableCommand { record_macro, "Record macro", replay_macro, "Replay macro", command_palette, "Open command pallete", - toggle_or_focus_explorer, "toggle or focus explorer", - open_explorer_recursion, "open explorer recursion", + toggle_or_focus_explorer, "Toggle or focus explorer", + focus_current_file, "Focus current file in explorer", close_explorer, "close explorer", ); } @@ -2232,13 +2232,20 @@ fn toggle_or_focus_explorer(cx: &mut Context) { )); } -fn open_explorer_recursion(cx: &mut Context) { +fn focus_current_file(cx: &mut Context) { cx.callback = Some(Box::new( |compositor: &mut Compositor, cx: &mut compositor::Context| { if let Some(editor) = compositor.find::() { - match ui::Explorer::new_explorer_recursion() { - Ok(explore) => editor.explorer = Some(overlayed(explore)), - Err(err) => cx.editor.set_error(format!("{}", err)), + match editor.explorer.as_mut() { + Some(explore) => explore.content.focus_current_file(cx), + None => match ui::Explorer::new(cx) { + Ok(explore) => { + let mut explorer = overlayed(explore); + explorer.content.focus_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 71f0f154b5b4..167ef51077c3 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" => open_explorer_recursion, + "E" => focus_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 ccdcb6cc7b41..af54d129c473 100644 --- a/helix-term/src/ui/explore.rs +++ b/helix-term/src/ui/explore.rs @@ -77,6 +77,7 @@ impl FileInfo { impl TreeItem for FileInfo { type Params = State; + fn text(&self, cx: &mut Context, selected: bool, state: &mut State) -> Spans { let text = self.get_text(); let theme = &cx.editor.theme; @@ -179,6 +180,10 @@ impl TreeItem for FileInfo { self.get_text().contains(s) } } + + fn text_string(&self) -> String { + self.get_text().to_string() + } } // #[derive(Default, Debug, Clone)] @@ -262,21 +267,15 @@ impl Explorer { }) } - pub fn new_explorer_recursion() -> Result { - let current_root = std::env::current_dir().unwrap_or_else(|_| "./".into()); - let parent = FileInfo::parent(¤t_root); - let root = FileInfo::root(current_root.clone()); - let mut tree = - Tree::build_from_root(root, usize::MAX / 2)?.with_enter_fn(Self::toggle_current); - tree.insert_current_level(parent); - Ok(Self { - tree, - state: State::new(true, current_root), - repeat_motion: None, - prompt: None, - on_next_key: None, - }) - // let mut root = vec![, FileInfo::root(p)]; + pub fn focus_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(); + } + } } // pub fn new_with_uri(uri: String) -> Result { @@ -289,7 +288,7 @@ impl Explorer { // } pub fn focus(&mut self) { - self.state.focus = true + self.state.focus = true; } pub fn unfocus(&mut self) { diff --git a/helix-term/src/ui/tree.rs b/helix-term/src/ui/tree.rs index e767e35d3bff..4bc531912b66 100644 --- a/helix-term/src/ui/tree.rs +++ b/helix-term/src/ui/tree.rs @@ -1,5 +1,5 @@ -use std::cmp::Ordering; use std::iter::Peekable; +use std::{cmp::Ordering, path::PathBuf}; use anyhow::Result; @@ -18,6 +18,7 @@ pub trait TreeItem: Sized { type Params; fn text(&self, cx: &mut Context, selected: bool, params: &mut Self::Params) -> Spans; + fn text_string(&self) -> String; fn is_child(&self, other: &Self) -> bool; fn cmp(&self, other: &Self) -> Ordering; @@ -253,15 +254,76 @@ impl Tree { iter.skip(start).position(f).map(|p| p + start) } } + + 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 + )), + } + } + + // Center the selection + self.winline = self.max_len / 2; + } } impl Tree { - pub fn on_enter(&mut self, cx: &mut Context, params: &mut T::Params) { + pub fn on_enter(&mut self, cx: &mut Context, params: &mut T::Params, selected_index: usize) { if self.items.is_empty() { return; } if let Some(next_level) = self.next_item().map(|elem| elem.level) { - let current = &mut self.items[self.selected]; + let current = &mut self.items[selected_index]; let current_level = current.level; if next_level > current_level { if let Some(mut on_folded_fn) = self.on_folded_fn.take() { @@ -275,13 +337,13 @@ impl Tree { if let Some(mut on_open_fn) = self.on_opened_fn.take() { let mut f = || { - let current = &mut self.items[self.selected]; + let current = &mut self.items[selected_index]; let items = match on_open_fn(&mut current.item, cx, params) { TreeOp::Restore => { let inserts = std::mem::take(&mut current.folded); let _: Vec<_> = self .items - .splice(self.selected + 1..self.selected + 1, inserts) + .splice(selected_index + 1..selected_index + 1, inserts) .collect(); return; } @@ -297,17 +359,17 @@ impl Tree { let inserts = vec_to_tree(items, current.level + 1); let _: Vec<_> = self .items - .splice(self.selected + 1..self.selected + 1, inserts) + .splice(selected_index + 1..selected_index + 1, inserts) .collect(); }; f(); self.on_opened_fn = Some(on_open_fn) } else { - let current = &mut self.items[self.selected]; + let current = &mut self.items[selected_index]; let inserts = std::mem::take(&mut current.folded); let _: Vec<_> = self .items - .splice(self.selected + 1..self.selected + 1, inserts) + .splice(selected_index + 1..selected_index + 1, inserts) .collect(); } } @@ -430,6 +492,10 @@ impl Tree { self.items[self.selected].item = item; } + pub fn set_selected(&mut self, selected: usize) { + self.selected = selected + } + pub fn insert_current_level(&mut self, item: T) { let current = self.current(); let level = current.level; @@ -567,7 +633,7 @@ impl Tree { key!('h') => self.move_left(1.max(count)), key!('l') => self.move_right(1.max(count)), shift!('G') => self.move_down(usize::MAX / 2), - key!(Enter) => self.on_enter(cx, params), + key!(Enter) => self.on_enter(cx, params, self.selected), ctrl!('d') => self.move_down_half_page(), ctrl!('u') => self.move_up_half_page(), shift!('D') => self.move_down_page(),