feat(explore): remove files/folder

pull/9/head
wongjiahau 1 year ago
parent 458fa1ca58
commit 2af8b41007

@ -223,8 +223,14 @@ impl TreeItem for FileInfo {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
enum PromptAction { enum PromptAction {
Search(bool), // search next/search pre Search(bool), // search next/search pre
CreateFolder { folder_path: PathBuf }, CreateFolder {
CreateFile { folder_path: PathBuf }, folder_path: PathBuf,
parent_index: usize,
},
CreateFile {
folder_path: PathBuf,
parent_index: usize,
},
RemoveDir, RemoveDir,
RemoveFile, RemoveFile,
Filter, Filter,
@ -306,7 +312,7 @@ impl Explorer {
Ok(_) => { Ok(_) => {
self.focus(); 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<()> { 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(( self.prompt = Some((
PromptAction::CreateFolder { PromptAction::CreateFolder {
parent_index,
folder_path: folder_path.clone(), folder_path: folder_path.clone(),
}, },
Prompt::new( Prompt::new(
@ -412,9 +419,10 @@ impl Explorer {
} }
fn new_create_file_prompt(&mut self) -> Result<()> { 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(( self.prompt = Some((
PromptAction::CreateFile { PromptAction::CreateFile {
parent_index,
folder_path: folder_path.clone(), folder_path: folder_path.clone(),
}, },
Prompt::new( Prompt::new(
@ -427,18 +435,36 @@ impl Explorer {
Ok(()) Ok(())
} }
fn current_parent_folder_path(&self) -> Result<PathBuf> { fn nearest_folder(&self) -> Result<(usize, PathBuf)> {
let current_item = self.tree.current_item(); let current = self.tree.current();
Ok(current_item if current.item().is_parent() {
.path Ok((current.index(), current.item().path.to_path_buf()))
.parent() } else {
.ok_or_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!( anyhow::anyhow!(format!(
"Unable to get parent directory of '{}'", "Unable to get parent path of '{}'",
current_item.path.to_string_lossy() 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) { fn new_remove_file_prompt(&mut self, cx: &mut Context) {
@ -458,7 +484,7 @@ impl Explorer {
cx.editor.set_error(format!("{e}")); cx.editor.set_error(format!("{e}"));
return; return;
} }
let p = format!("remove file: {}, YES? ", item.path.display()); let p = format!(" Delete file: '{}'? y/n: ", item.path.display());
self.prompt = Some(( self.prompt = Some((
PromptAction::RemoveFile, PromptAction::RemoveFile,
Prompt::new(p.into(), None, ui::completers::none, |_, _, _| {}), Prompt::new(p.into(), None, ui::completers::none, |_, _, _| {}),
@ -486,7 +512,7 @@ impl Explorer {
cx.editor.set_error(format!("{e}")); cx.editor.set_error(format!("{e}"));
return; return;
} }
let p = format!("remove dir: {}, YES? ", item.path.display()); let p = format!(" Delete folder: '{}'? y/n: ", item.path.display());
self.prompt = Some(( self.prompt = Some((
PromptAction::RemoveDir, PromptAction::RemoveDir,
Prompt::new(p.into(), None, ui::completers::none, |_, _, _| {}), Prompt::new(p.into(), None, ui::completers::none, |_, _, _| {}),
@ -738,27 +764,41 @@ impl Explorer {
}; };
let line = prompt.line(); let line = prompt.line();
match (&action, event.into()) { 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}")) 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}")) cx.editor.set_error(format!("{e}"))
} }
} }
(PromptAction::RemoveDir, key!(Enter)) => { (PromptAction::RemoveDir, key!(Enter)) => {
let item = self.tree.current_item(); if line == "y" {
if let Err(e) = std::fs::remove_dir_all(&item.path) { let item = self.tree.current_item();
cx.editor.set_error(format!("{e}")); if let Err(e) = std::fs::remove_dir_all(&item.path) {
} else { cx.editor.set_error(format!("{e}"));
self.tree.fold_current_child(); } else {
self.tree.remove_current(); self.tree.fold_current_child();
self.tree.remove_current();
}
} }
} }
(PromptAction::RemoveFile, key!(Enter)) => { (PromptAction::RemoveFile, key!(Enter)) => {
if line == "YES" { if line == "y" {
let item = self.tree.current_item(); let item = self.tree.current_item();
if let Err(e) = std::fs::remove_file(&item.path) { if let Err(e) = std::fs::remove_file(&item.path) {
cx.editor.set_error(format!("{e}")); cx.editor.set_error(format!("{e}"));
@ -776,7 +816,13 @@ impl Explorer {
EventResult::Consumed(None) 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 current = self.tree.current_item();
let p = helix_core::path::get_normalized_path(&current_parent.join(file_name)); let p = helix_core::path::get_normalized_path(&current_parent.join(file_name));
match p.parent() { match p.parent() {
@ -795,7 +841,7 @@ impl Explorer {
if current.file_type == FileType::Placeholder { if current.file_type == FileType::Placeholder {
self.tree.replace_current(file); self.tree.replace_current(file);
} else { } else {
self.tree.add_sibling_to_current_item(file)?; self.tree.add_child(parent_index, file)?;
} }
Ok(()) Ok(())
} }
@ -841,11 +887,6 @@ impl Component for Explorer {
self.repeat_motion = Some(repeat_motion); 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!('f') => self.new_filter_prompt(),
key!('/') => self.new_search_prompt(true), key!('/') => self.new_search_prompt(true),
key!('?') => self.new_search_prompt(false), key!('?') => self.new_search_prompt(false),
@ -859,7 +900,13 @@ impl Component for Explorer {
cx.editor.set_error(error.to_string()) 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') => { key!('r') => {
self.on_next_key = Some(Box::new(|cx, explorer, event| { self.on_next_key = Some(Box::new(|cx, explorer, event| {
match event.into() { match event.into() {

@ -54,37 +54,6 @@ fn vec_to_tree<T: TreeItem>(mut items: Vec<T>) -> Vec<Tree<T>> {
) )
} }
// return total elems's count contain self
fn get_elems_recursion<T: TreeItem>(t: &mut Tree<T>, depth: usize) -> Result<usize> {
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<T: TreeItem>(dist: &mut Vec<Tree<T>>, mut t: Tree<T>) {
let childs = std::mem::take(&mut t.children);
dist.push(t);
for child in childs {
expand_elems(dist, child)
}
}
pub enum TreeOp<T> { pub enum TreeOp<T> {
Noop, Noop,
Restore, Restore,
@ -187,6 +156,24 @@ impl<T: Clone> Tree<T> {
None None
} }
} }
pub fn parent_index(&self) -> Option<usize> {
self.parent_index
}
pub fn index(&self) -> usize {
self.index
}
}
impl<T: TreeItem> Tree<T> {
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<T> Tree<T> { impl<T> Tree<T> {
@ -195,7 +182,7 @@ impl<T> Tree<T> {
item, item,
index: 0, index: 0,
parent_index: None, parent_index: None,
children: index_elems(1, children), children: index_elems(0, children),
is_opened: false, is_opened: false,
} }
} }
@ -252,7 +239,26 @@ impl<T> Tree<T> {
fn regenerate_index(&mut self) { fn regenerate_index(&mut self) {
let items = std::mem::take(&mut self.children); 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<T: TreeItem> TreeView<T> {
/// ``` /// ```
/// vec!["helix-term", "src", "ui", "tree.rs"] /// 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 // Expand the tree
segments.iter().fold( segments.iter().fold(
Ok(&mut self.tree), Ok(&mut self.tree),
@ -359,20 +365,15 @@ impl<T: TreeItem> TreeView<T> {
{ {
Some(tree) => { Some(tree) => {
if !tree.is_opened { if !tree.is_opened {
tree.children = vec_to_tree( tree.open()?;
tree.item.get_children().map_err(|err| err.to_string())?,
);
if !tree.children.is_empty() {
tree.is_opened = true;
}
} }
Ok(tree) Ok(tree)
} }
None => Err(format!( None => Err(anyhow::anyhow!(format!(
"Unable to find path: '{}'. current_segment = {}", "Unable to find path: '{}'. current_segment = {}",
segments.join("/"), segments.join("/"),
segment segment
)), ))),
} }
} }
}, },
@ -416,23 +417,19 @@ impl<T: TreeItem> TreeView<T> {
} }
} }
fn go_to_children(&mut self, cx: &mut Context) { fn go_to_children(&mut self, cx: &mut Context) -> Result<()> {
let current = self.current_mut(); let current = self.current_mut();
if current.is_opened { if current.is_opened {
self.selected += 1; self.selected += 1;
return; Ok(())
} } else {
let items = match current.item.get_children() { current.open()?;
Ok(items) => items, if !current.children.is_empty() {
Err(e) => return cx.editor.set_error(format!("{e}")), self.selected += 1;
}; self.regenerate_index();
if items.is_empty() { }
return; Ok(())
} }
current.is_opened = true;
current.children = vec_to_tree(items);
self.selected += 1;
self.regenerate_index()
} }
} }
@ -639,11 +636,8 @@ impl<T: TreeItem> TreeView<T> {
self.winline self.winline
} }
pub fn remove_current(&mut self) -> T { pub fn remove_current(&mut self) {
todo!() self.tree.remove(self.selected)
// let elem = self.tree.remove(self.selected);
// self.selected = self.selected.saturating_sub(1);
// elem.item
} }
pub fn replace_current(&mut self, item: T) { pub fn replace_current(&mut self, item: T) {
@ -654,25 +648,26 @@ impl<T: TreeItem> TreeView<T> {
self.selected = selected self.selected = selected
} }
pub fn add_sibling_to_current_item(&mut self, item: T) -> Result<()> { pub fn add_child(&mut self, index: usize, item: T) -> Result<()> {
let current = self.current(); match self.tree.get_mut(index) {
match current.parent_index {
None => Err(anyhow::anyhow!(format!( None => Err(anyhow::anyhow!(format!(
"Current item = '{}' has no parent", "No item found at index = {}",
current.item.name() index
))), ))),
Some(parent_index) => { Some(tree) => {
let parent = self.get_mut(parent_index);
let item_name = item.name(); let item_name = item.name();
parent.children.push(Tree::new(item, vec![])); if !tree.is_opened {
parent tree.open()?;
.children }
tree.children.push(Tree::new(item, vec![]));
tree.children
.sort_by(|a, b| tree_item_cmp(&a.item, &b.item)); .sort_by(|a, b| tree_item_cmp(&a.item, &b.item));
self.regenerate_index(); self.regenerate_index();
let parent = self.get_mut(parent_index);
let tree = self.get_mut(index);
// Focus the added sibling // Focus the added sibling
if let Some(tree) = parent if let Some(tree) = tree
.children .children
.iter() .iter()
.find(|tree| tree.item.name().eq(&item_name)) .find(|tree| tree.item.name().eq(&item_name))
@ -903,7 +898,10 @@ impl<T: TreeItem> TreeView<T> {
})); }));
} }
key!('h') => self.go_to_parent(), 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), key!(Enter) => self.on_enter(cx, params, self.selected),
ctrl!('d') => self.move_down_half_page(), ctrl!('d') => self.move_down_half_page(),
ctrl!('u') => self.move_up_half_page(), ctrl!('u') => self.move_up_half_page(),
@ -971,25 +969,24 @@ impl<T: TreeItem + Clone> TreeView<T> {
/// jar (3) /// jar (3)
/// yo (4) /// yo (4)
/// ``` /// ```
fn index_elems<T>(start_index: usize, elems: Vec<Tree<T>>) -> Vec<Tree<T>> { fn index_elems<T>(parent_index: usize, elems: Vec<Tree<T>>) -> Vec<Tree<T>> {
fn index_elems<'a, T>( fn index_elems<'a, T>(
current_index: usize, current_index: usize,
elems: Vec<Tree<T>>, elems: Vec<Tree<T>>,
parent_index: Option<usize>, parent_index: usize,
) -> (usize, Vec<Tree<T>>) { ) -> (usize, Vec<Tree<T>>) {
elems elems
.into_iter() .into_iter()
.fold((current_index, vec![]), |(current_index, trees), elem| { .fold((current_index, vec![]), |(current_index, trees), elem| {
let index = current_index; let index = current_index;
let item = elem.item; let item = elem.item;
let (current_index, folded) = let (current_index, folded) = index_elems(current_index + 1, elem.children, index);
index_elems(current_index + 1, elem.children, Some(index));
let tree = Tree { let tree = Tree {
item, item,
children: folded, children: folded,
index, index,
is_opened: elem.is_opened, is_opened: elem.is_opened,
parent_index, parent_index: Some(parent_index),
}; };
( (
current_index, current_index,
@ -997,7 +994,7 @@ fn index_elems<T>(start_index: usize, elems: Vec<Tree<T>>) -> Vec<Tree<T>> {
) )
}) })
} }
index_elems(start_index, elems, None).1 index_elems(parent_index + 1, elems, parent_index).1
} }
#[cfg(test)] #[cfg(test)]
@ -1008,8 +1005,8 @@ mod test_tree {
#[test] #[test]
fn test_indexs_elems() { fn test_indexs_elems() {
let result = index_elems( let result = Tree::new(
0, "root",
vec![ vec![
Tree::new("foo", vec![Tree::new("bar", vec![])]), Tree::new("foo", vec![Tree::new("bar", vec![])]),
Tree::new( Tree::new(
@ -1018,43 +1015,12 @@ mod test_tree {
), ),
], ],
); );
assert_eq!( assert_eq!(result.get(0).unwrap().item, "root");
result, assert_eq!(result.get(1).unwrap().item, "foo");
vec![ assert_eq!(result.get(2).unwrap().item, "bar");
Tree { assert_eq!(result.get(3).unwrap().item, "spam");
item: "foo", assert_eq!(result.get(4).unwrap().item, "jar");
is_opened: false, assert_eq!(result.get(5).unwrap().item, "yo");
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)
}]
}]
}
]
)
} }
#[test] #[test]
@ -1224,4 +1190,40 @@ mod test_tree {
let result = Tree::filter(&tree, &|item| item.to_lowercase().contains("helix")); let result = Tree::filter(&tree, &|item| item.to_lowercase().contains("helix"));
assert_eq!(result, None) 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![]),],
)
)
}
} }

Loading…
Cancel
Save