From 2af8b410074e8b39e41dcee63b72d87351353c83 Mon Sep 17 00:00:00 2001 From: wongjiahau Date: Mon, 13 Feb 2023 10:44:27 +0800 Subject: [PATCH] feat(explore): remove files/folder --- helix-term/src/ui/explore.rs | 119 ++++++++++++------ helix-term/src/ui/tree.rs | 238 ++++++++++++++++++----------------- 2 files changed, 203 insertions(+), 154 deletions(-) diff --git a/helix-term/src/ui/explore.rs b/helix-term/src/ui/explore.rs index 5e80137d..a04a605f 100644 --- a/helix-term/src/ui/explore.rs +++ b/helix-term/src/ui/explore.rs @@ -223,8 +223,14 @@ impl TreeItem for FileInfo { #[derive(Clone, Debug)] enum PromptAction { Search(bool), // search next/search pre - CreateFolder { folder_path: PathBuf }, - CreateFile { folder_path: PathBuf }, + CreateFolder { + folder_path: PathBuf, + parent_index: usize, + }, + CreateFile { + folder_path: PathBuf, + parent_index: usize, + }, RemoveDir, RemoveFile, Filter, @@ -306,7 +312,7 @@ impl Explorer { Ok(_) => { self.focus(); } - Err(error) => cx.editor.set_error(error), + Err(error) => cx.editor.set_error(error.to_string()), } } } @@ -396,9 +402,10 @@ impl Explorer { } fn new_create_folder_prompt(&mut self) -> Result<()> { - let folder_path = self.current_parent_folder_path()?; + let (parent_index, folder_path) = self.nearest_folder()?; self.prompt = Some(( PromptAction::CreateFolder { + parent_index, folder_path: folder_path.clone(), }, Prompt::new( @@ -412,9 +419,10 @@ impl Explorer { } fn new_create_file_prompt(&mut self) -> Result<()> { - let folder_path = self.current_parent_folder_path()?; + let (parent_index, folder_path) = self.nearest_folder()?; self.prompt = Some(( PromptAction::CreateFile { + parent_index, folder_path: folder_path.clone(), }, Prompt::new( @@ -427,18 +435,36 @@ impl Explorer { Ok(()) } - fn current_parent_folder_path(&self) -> Result { - let current_item = self.tree.current_item(); - Ok(current_item - .path - .parent() - .ok_or_else(|| { + fn nearest_folder(&self) -> Result<(usize, PathBuf)> { + let current = self.tree.current(); + if current.item().is_parent() { + Ok((current.index(), current.item().path.to_path_buf())) + } else { + let parent_index = current.parent_index().ok_or_else(|| { + anyhow::anyhow!(format!( + "Unable to get parent index of '{}'", + current.item().path.to_string_lossy() + )) + })?; + let parent_path = current.item().path.parent().ok_or_else(|| { anyhow::anyhow!(format!( - "Unable to get parent directory of '{}'", - current_item.path.to_string_lossy() + "Unable to get parent path of '{}'", + current.item().path.to_string_lossy() )) - })? - .to_path_buf()) + })?; + Ok((parent_index, parent_path.to_path_buf())) + } + } + + fn new_remove_prompt(&mut self, cx: &mut Context) { + let item = self.tree.current().item(); + match item.file_type { + FileType::Dir => self.new_remove_dir_prompt(cx), + FileType::Exe | FileType::File => self.new_remove_file_prompt(cx), + FileType::Placeholder => cx.editor.set_error("Placeholder is not removable."), + FileType::Parent => cx.editor.set_error("Parent is not removable."), + FileType::Root => cx.editor.set_error("Root is not removable"), + } } fn new_remove_file_prompt(&mut self, cx: &mut Context) { @@ -458,7 +484,7 @@ impl Explorer { cx.editor.set_error(format!("{e}")); return; } - let p = format!("remove file: {}, YES? ", item.path.display()); + let p = format!(" Delete file: '{}'? y/n: ", item.path.display()); self.prompt = Some(( PromptAction::RemoveFile, Prompt::new(p.into(), None, ui::completers::none, |_, _, _| {}), @@ -486,7 +512,7 @@ impl Explorer { cx.editor.set_error(format!("{e}")); return; } - let p = format!("remove dir: {}, YES? ", item.path.display()); + let p = format!(" Delete folder: '{}'? y/n: ", item.path.display()); self.prompt = Some(( PromptAction::RemoveDir, Prompt::new(p.into(), None, ui::completers::none, |_, _, _| {}), @@ -738,27 +764,41 @@ impl Explorer { }; let line = prompt.line(); match (&action, event.into()) { - (PromptAction::CreateFolder { folder_path }, key!(Enter)) => { - if let Err(e) = self.new_path(folder_path.clone(), line, true) { + ( + PromptAction::CreateFolder { + folder_path, + parent_index, + }, + key!(Enter), + ) => { + if let Err(e) = self.new_path(folder_path.clone(), line, true, *parent_index) { cx.editor.set_error(format!("{e}")) } } - (PromptAction::CreateFile { folder_path }, key!(Enter)) => { - if let Err(e) = self.new_path(folder_path.clone(), line, false) { + ( + PromptAction::CreateFile { + folder_path, + parent_index, + }, + key!(Enter), + ) => { + if let Err(e) = self.new_path(folder_path.clone(), line, false, *parent_index) { cx.editor.set_error(format!("{e}")) } } (PromptAction::RemoveDir, key!(Enter)) => { - let item = self.tree.current_item(); - if let Err(e) = std::fs::remove_dir_all(&item.path) { - cx.editor.set_error(format!("{e}")); - } else { - self.tree.fold_current_child(); - self.tree.remove_current(); + if line == "y" { + let item = self.tree.current_item(); + if let Err(e) = std::fs::remove_dir_all(&item.path) { + cx.editor.set_error(format!("{e}")); + } else { + self.tree.fold_current_child(); + self.tree.remove_current(); + } } } (PromptAction::RemoveFile, key!(Enter)) => { - if line == "YES" { + if line == "y" { let item = self.tree.current_item(); if let Err(e) = std::fs::remove_file(&item.path) { cx.editor.set_error(format!("{e}")); @@ -776,7 +816,13 @@ impl Explorer { EventResult::Consumed(None) } - fn new_path(&mut self, current_parent: PathBuf, file_name: &str, is_dir: bool) -> Result<()> { + fn new_path( + &mut self, + current_parent: PathBuf, + file_name: &str, + is_dir: bool, + parent_index: usize, + ) -> Result<()> { let current = self.tree.current_item(); let p = helix_core::path::get_normalized_path(¤t_parent.join(file_name)); match p.parent() { @@ -795,7 +841,7 @@ impl Explorer { if current.file_type == FileType::Placeholder { self.tree.replace_current(file); } else { - self.tree.add_sibling_to_current_item(file)?; + self.tree.add_child(parent_index, file)?; } Ok(()) } @@ -841,11 +887,6 @@ impl Component for Explorer { self.repeat_motion = Some(repeat_motion); } } - key!(Backspace) => { - if let Some(parent) = self.state.current_root.parent().clone() { - self.change_root(cx, parent.to_path_buf()) - } - } key!('f') => self.new_filter_prompt(), key!('/') => self.new_search_prompt(true), key!('?') => self.new_search_prompt(false), @@ -859,7 +900,13 @@ impl Component for Explorer { cx.editor.set_error(error.to_string()) } } - key!('o') => self.change_root(cx, self.tree.current_item().path.clone()), + key!('[') => { + if let Some(parent) = self.state.current_root.parent().clone() { + self.change_root(cx, parent.to_path_buf()) + } + } + key!(']') => self.change_root(cx, self.tree.current_item().path.clone()), + key!('d') => self.new_remove_prompt(cx), key!('r') => { self.on_next_key = Some(Box::new(|cx, explorer, event| { match event.into() { diff --git a/helix-term/src/ui/tree.rs b/helix-term/src/ui/tree.rs index cbc97a0b..16251669 100644 --- a/helix-term/src/ui/tree.rs +++ b/helix-term/src/ui/tree.rs @@ -54,37 +54,6 @@ fn vec_to_tree(mut items: Vec) -> Vec> { ) } -// return total elems's count contain self -fn get_elems_recursion(t: &mut Tree, depth: usize) -> Result { - let mut childs = t.item.get_children()?; - childs.sort_by(tree_item_cmp); - let mut elems = Vec::with_capacity(childs.len()); - // let level = t.level + 1; - let level = todo!(); - - let mut total = 1; - for child in childs { - let mut elem = Tree::new(child, level); - let count = if depth > 0 { - get_elems_recursion(&mut elem, depth - 1)? - } else { - 1 - }; - elems.push(elem); - total += count; - } - t.children = elems; - Ok(total) -} - -fn expand_elems(dist: &mut Vec>, mut t: Tree) { - let childs = std::mem::take(&mut t.children); - dist.push(t); - for child in childs { - expand_elems(dist, child) - } -} - pub enum TreeOp { Noop, Restore, @@ -187,6 +156,24 @@ impl Tree { None } } + + pub fn parent_index(&self) -> Option { + self.parent_index + } + + pub fn index(&self) -> usize { + self.index + } +} + +impl Tree { + fn open(&mut self) -> Result<()> { + self.children = vec_to_tree(self.item.get_children()?); + if !self.children.is_empty() { + self.is_opened = true; + } + Ok(()) + } } impl Tree { @@ -195,7 +182,7 @@ impl Tree { item, index: 0, parent_index: None, - children: index_elems(1, children), + children: index_elems(0, children), is_opened: false, } } @@ -252,7 +239,26 @@ impl Tree { fn regenerate_index(&mut self) { let items = std::mem::take(&mut self.children); - self.children = index_elems(1, items); + self.children = index_elems(0, items); + } + + fn remove(&mut self, index: usize) { + let children = std::mem::replace(&mut self.children, vec![]); + self.children = children + .into_iter() + .filter_map(|tree| { + if tree.index == index { + None + } else { + Some(tree) + } + }) + .map(|mut tree| { + tree.remove(index); + tree + }) + .collect(); + self.regenerate_index() } } @@ -345,7 +351,7 @@ impl TreeView { /// ``` /// vec!["helix-term", "src", "ui", "tree.rs"] /// ``` - pub fn reveal_item(&mut self, segments: Vec<&str>) -> Result<(), String> { + pub fn reveal_item(&mut self, segments: Vec<&str>) -> Result<()> { // Expand the tree segments.iter().fold( Ok(&mut self.tree), @@ -359,20 +365,15 @@ impl TreeView { { 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; - } + tree.open()?; } Ok(tree) } - None => Err(format!( + None => Err(anyhow::anyhow!(format!( "Unable to find path: '{}'. current_segment = {}", segments.join("/"), segment - )), + ))), } } }, @@ -416,23 +417,19 @@ impl TreeView { } } - fn go_to_children(&mut self, cx: &mut Context) { + fn go_to_children(&mut self, cx: &mut Context) -> Result<()> { let current = self.current_mut(); if current.is_opened { self.selected += 1; - return; - } - let items = match current.item.get_children() { - Ok(items) => items, - Err(e) => return cx.editor.set_error(format!("{e}")), - }; - if items.is_empty() { - return; + Ok(()) + } else { + current.open()?; + if !current.children.is_empty() { + self.selected += 1; + self.regenerate_index(); + } + Ok(()) } - current.is_opened = true; - current.children = vec_to_tree(items); - self.selected += 1; - self.regenerate_index() } } @@ -639,11 +636,8 @@ impl TreeView { self.winline } - pub fn remove_current(&mut self) -> T { - todo!() - // let elem = self.tree.remove(self.selected); - // self.selected = self.selected.saturating_sub(1); - // elem.item + pub fn remove_current(&mut self) { + self.tree.remove(self.selected) } pub fn replace_current(&mut self, item: T) { @@ -654,25 +648,26 @@ impl TreeView { self.selected = selected } - pub fn add_sibling_to_current_item(&mut self, item: T) -> Result<()> { - let current = self.current(); - match current.parent_index { + pub fn add_child(&mut self, index: usize, item: T) -> Result<()> { + match self.tree.get_mut(index) { None => Err(anyhow::anyhow!(format!( - "Current item = '{}' has no parent", - current.item.name() + "No item found at index = {}", + index ))), - Some(parent_index) => { - let parent = self.get_mut(parent_index); + Some(tree) => { let item_name = item.name(); - parent.children.push(Tree::new(item, vec![])); - parent - .children + if !tree.is_opened { + tree.open()?; + } + tree.children.push(Tree::new(item, vec![])); + tree.children .sort_by(|a, b| tree_item_cmp(&a.item, &b.item)); self.regenerate_index(); - let parent = self.get_mut(parent_index); + + let tree = self.get_mut(index); // Focus the added sibling - if let Some(tree) = parent + if let Some(tree) = tree .children .iter() .find(|tree| tree.item.name().eq(&item_name)) @@ -903,7 +898,10 @@ impl TreeView { })); } key!('h') => self.go_to_parent(), - key!('l') => self.go_to_children(cx), + key!('l') => match self.go_to_children(cx) { + Ok(_) => {} + Err(err) => cx.editor.set_error(err.to_string()), + }, key!(Enter) => self.on_enter(cx, params, self.selected), ctrl!('d') => self.move_down_half_page(), ctrl!('u') => self.move_up_half_page(), @@ -971,25 +969,24 @@ impl TreeView { /// jar (3) /// yo (4) /// ``` -fn index_elems(start_index: usize, elems: Vec>) -> Vec> { +fn index_elems(parent_index: usize, elems: Vec>) -> Vec> { fn index_elems<'a, T>( current_index: usize, elems: Vec>, - parent_index: Option, + parent_index: usize, ) -> (usize, Vec>) { elems .into_iter() .fold((current_index, vec![]), |(current_index, trees), elem| { let index = current_index; let item = elem.item; - let (current_index, folded) = - index_elems(current_index + 1, elem.children, Some(index)); + let (current_index, folded) = index_elems(current_index + 1, elem.children, index); let tree = Tree { item, children: folded, index, is_opened: elem.is_opened, - parent_index, + parent_index: Some(parent_index), }; ( current_index, @@ -997,7 +994,7 @@ fn index_elems(start_index: usize, elems: Vec>) -> Vec> { ) }) } - index_elems(start_index, elems, None).1 + index_elems(parent_index + 1, elems, parent_index).1 } #[cfg(test)] @@ -1008,8 +1005,8 @@ mod test_tree { #[test] fn test_indexs_elems() { - let result = index_elems( - 0, + let result = Tree::new( + "root", vec![ Tree::new("foo", vec![Tree::new("bar", vec![])]), Tree::new( @@ -1018,43 +1015,12 @@ mod test_tree { ), ], ); - assert_eq!( - result, - vec![ - Tree { - item: "foo", - is_opened: false, - index: 0, - parent_index: None, - children: vec![Tree { - item: "bar", - is_opened: false, - index: 1, - parent_index: Some(0), - children: vec![] - }] - }, - Tree { - item: "spam", - is_opened: false, - index: 2, - parent_index: None, - children: vec![Tree { - item: "jar", - is_opened: false, - index: 3, - parent_index: Some(2), - children: vec![Tree { - item: "yo", - is_opened: false, - index: 4, - children: vec![], - parent_index: Some(3) - }] - }] - } - ] - ) + assert_eq!(result.get(0).unwrap().item, "root"); + assert_eq!(result.get(1).unwrap().item, "foo"); + assert_eq!(result.get(2).unwrap().item, "bar"); + assert_eq!(result.get(3).unwrap().item, "spam"); + assert_eq!(result.get(4).unwrap().item, "jar"); + assert_eq!(result.get(5).unwrap().item, "yo"); } #[test] @@ -1224,4 +1190,40 @@ mod test_tree { let result = Tree::filter(&tree, &|item| item.to_lowercase().contains("helix")); assert_eq!(result, None) } + + #[test] + fn test_remove() { + let mut tree = Tree::new( + ".cargo", + vec![ + Tree::new("spam", vec![Tree::new("Cargo.toml", vec![])]), + Tree::new("Cargo.toml", vec![Tree::new("pam", vec![])]), + Tree::new("hello", vec![]), + ], + ); + + tree.remove(2); + + assert_eq!( + tree, + Tree::new( + ".cargo", + vec![ + Tree::new("spam", vec![]), + Tree::new("Cargo.toml", vec![Tree::new("pam", vec![])]), + Tree::new("hello", vec![]), + ], + ) + ); + + tree.remove(2); + + assert_eq!( + tree, + Tree::new( + ".cargo", + vec![Tree::new("spam", vec![]), Tree::new("hello", vec![]),], + ) + ) + } }