diff --git a/helix-term/src/ui/explore.rs b/helix-term/src/ui/explore.rs index f8faa82d..5e80137d 100644 --- a/helix-term/src/ui/explore.rs +++ b/helix-term/src/ui/explore.rs @@ -175,7 +175,7 @@ impl TreeItem for FileInfo { Ok(ret) } - fn text_string(&self) -> String { + fn name(&self) -> String { self.get_text().to_string() } @@ -220,11 +220,11 @@ impl TreeItem for FileInfo { // } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Debug)] enum PromptAction { Search(bool), // search next/search pre - Mkdir, - CreateFile, + CreateFolder { folder_path: PathBuf }, + CreateFile { folder_path: PathBuf }, RemoveDir, RemoveFile, Filter, @@ -258,10 +258,8 @@ pub struct Explorer { impl Explorer { pub fn new(cx: &mut Context) -> Result { let current_root = std::env::current_dir().unwrap_or_else(|_| "./".into()); - let root = FileInfo::root(current_root.clone()); - let children = root.get_children()?; Ok(Self { - tree: TreeView::build_tree(root, children).with_enter_fn(Self::toggle_current), + tree: Self::new_tree(current_root.clone())?, state: State::new(true, current_root), repeat_motion: None, prompt: None, @@ -269,6 +267,22 @@ impl Explorer { }) } + fn new_tree(root: PathBuf) -> Result> { + let root = FileInfo::root(root.clone()); + let children = root.get_children()?; + Ok(TreeView::build_tree(root, children).with_enter_fn(Self::toggle_current)) + } + + fn change_root(&mut self, cx: &mut Context, root: PathBuf) { + match Self::new_tree(root.clone()) { + Ok(tree) => { + self.state.current_root = root; + self.tree = tree; + } + Err(e) => cx.editor.set_error(format!("{e}")), + } + } + pub fn reveal_current_file(&mut self, cx: &mut Context) { let current_document_path = doc!(cx.editor).path().cloned(); match current_document_path { @@ -381,23 +395,50 @@ impl Explorer { )) } - fn new_mkdir_prompt(&mut self) { + fn new_create_folder_prompt(&mut self) -> Result<()> { + let folder_path = self.current_parent_folder_path()?; self.prompt = Some(( - PromptAction::Mkdir, - Prompt::new("mkdir: ".into(), None, ui::completers::none, |_, _, _| {}), + PromptAction::CreateFolder { + folder_path: folder_path.clone(), + }, + Prompt::new( + format!(" New folder: {}/", folder_path.to_string_lossy()).into(), + None, + ui::completers::none, + |_, _, _| {}, + ), )); + Ok(()) } - fn new_create_file_prompt(&mut self) { + fn new_create_file_prompt(&mut self) -> Result<()> { + let folder_path = self.current_parent_folder_path()?; self.prompt = Some(( - PromptAction::CreateFile, + PromptAction::CreateFile { + folder_path: folder_path.clone(), + }, Prompt::new( - "create file: ".into(), + format!(" New file: {}/", folder_path.to_string_lossy()).into(), None, ui::completers::none, |_, _, _| {}, ), )); + Ok(()) + } + + fn current_parent_folder_path(&self) -> Result { + let current_item = self.tree.current_item(); + Ok(current_item + .path + .parent() + .ok_or_else(|| { + anyhow::anyhow!(format!( + "Unable to get parent directory of '{}'", + current_item.path.to_string_lossy() + )) + })? + .to_path_buf()) } fn new_remove_file_prompt(&mut self, cx: &mut Context) { @@ -696,14 +737,14 @@ impl Explorer { _ => return EventResult::Ignored(None), }; let line = prompt.line(); - match (action, event.into()) { - (PromptAction::Mkdir, key!(Enter)) => { - if let Err(e) = self.new_path(line, true) { + match (&action, event.into()) { + (PromptAction::CreateFolder { folder_path }, key!(Enter)) => { + if let Err(e) = self.new_path(folder_path.clone(), line, true) { cx.editor.set_error(format!("{e}")) } } - (PromptAction::CreateFile, key!(Enter)) => { - if let Err(e) = self.new_path(line, false) { + (PromptAction::CreateFile { folder_path }, key!(Enter)) => { + if let Err(e) = self.new_path(folder_path.clone(), line, false) { cx.editor.set_error(format!("{e}")) } } @@ -735,23 +776,15 @@ impl Explorer { EventResult::Consumed(None) } - fn new_path(&mut self, file_name: &str, is_dir: bool) -> Result<()> { + fn new_path(&mut self, current_parent: PathBuf, file_name: &str, is_dir: bool) -> Result<()> { let current = self.tree.current_item(); - let current_parent = if current.file_type == FileType::Placeholder { - ¤t.path - } else { - current - .path - .parent() - .ok_or_else(|| anyhow::anyhow!("can not get parent dir"))? - }; let p = helix_core::path::get_normalized_path(¤t_parent.join(file_name)); match p.parent() { Some(p) if p == current_parent => {} _ => bail!("The file name is not illegal"), }; - let f = if is_dir { + let file = if is_dir { std::fs::create_dir(&p)?; FileInfo::new(p, FileType::Dir) } else { @@ -760,9 +793,9 @@ impl Explorer { FileInfo::new(p, FileType::File) }; if current.file_type == FileType::Placeholder { - self.tree.replace_current(f); + self.tree.replace_current(file); } else { - self.tree.insert_current_level(f); + self.tree.add_sibling_to_current_item(file)?; } Ok(()) } @@ -808,33 +841,25 @@ 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!(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), - key!('m') => { - self.on_next_key = Some(Box::new(|_, explorer, event| { - match event.into() { - key!('d') => explorer.new_mkdir_prompt(), - key!('f') => explorer.new_create_file_prompt(), - _ => return EventResult::Ignored(None), - }; - EventResult::Consumed(None) - })); + key!('a') => { + if let Err(error) = self.new_create_file_prompt() { + cx.editor.set_error(error.to_string()) + } + } + shift!('A') => { + if let Err(error) = self.new_create_folder_prompt() { + cx.editor.set_error(error.to_string()) + } } + key!('o') => self.change_root(cx, self.tree.current_item().path.clone()), 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 1381762d..cbc97a0b 100644 --- a/helix-term/src/ui/tree.rs +++ b/helix-term/src/ui/tree.rs @@ -18,15 +18,13 @@ 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 name(&self) -> String; fn is_child(&self, other: &Self) -> bool; fn is_parent(&self) -> bool; fn cmp(&self, other: &Self) -> Ordering; fn filter(&self, s: &str) -> bool { - self.text_string() - .to_lowercase() - .contains(&s.to_lowercase()) + self.name().to_lowercase().contains(&s.to_lowercase()) } fn get_children(&self) -> Result> { @@ -357,7 +355,7 @@ impl TreeView { match current_tree .children .iter_mut() - .find(|tree| tree.item.text_string().eq(segment)) + .find(|tree| tree.item.name().eq(segment)) { Some(tree) => { if !tree.is_opened { @@ -387,7 +385,7 @@ impl TreeView { .fold(&self.tree, |tree, segment| { tree.children .iter() - .find(|tree| tree.item.text_string().eq(segment)) + .find(|tree| tree.item.name().eq(segment)) .expect("Should be unreachable") }) .index; @@ -656,13 +654,34 @@ impl TreeView { self.selected = selected } - pub fn insert_current_level(&mut self, item: T) { - let current = self.current_mut(); - current.children.push(Tree::new(item, vec![])); - current - .children - .sort_by(|a, b| tree_item_cmp(&a.item, &b.item)); - self.regenerate_index() + pub fn add_sibling_to_current_item(&mut self, item: T) -> Result<()> { + let current = self.current(); + match current.parent_index { + None => Err(anyhow::anyhow!(format!( + "Current item = '{}' has no parent", + current.item.name() + ))), + Some(parent_index) => { + let parent = self.get_mut(parent_index); + let item_name = item.name(); + parent.children.push(Tree::new(item, vec![])); + parent + .children + .sort_by(|a, b| tree_item_cmp(&a.item, &b.item)); + self.regenerate_index(); + let parent = self.get_mut(parent_index); + + // Focus the added sibling + if let Some(tree) = parent + .children + .iter() + .find(|tree| tree.item.name().eq(&item_name)) + { + self.selected = tree.index + }; + Ok(()) + } + } } } @@ -738,7 +757,7 @@ impl TreeView { selected: selected == tree.index, name: format!( "{}{}", - tree.item.text_string(), + tree.item.name(), if tree.item.is_parent() { format!("{}", std::path::MAIN_SEPARATOR) } else { @@ -910,9 +929,7 @@ impl TreeView { } let new_tree = Tree::filter(&self.tree, &|item: &T| { - item.text_string() - .to_lowercase() - .contains(&s.to_lowercase()) + item.name().to_lowercase().contains(&s.to_lowercase()) }) .unwrap_or_else(|| Tree { item: self.tree.item.clone(),