diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index ae725291..9de52928 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -433,7 +433,7 @@ impl MappableCommand { record_macro, "Record macro", replay_macro, "Replay macro", command_palette, "Open command pallete", - toggle_or_focus_explorer, "Toggle or focus explorer", + open_or_focus_explorer, "Open or focus explorer", reveal_current_file, "Reveal current file in explorer", close_explorer, "close explorer", ); @@ -2216,7 +2216,7 @@ fn file_picker_in_current_directory(cx: &mut Context) { cx.push_layer(Box::new(overlayed(picker))); } -fn toggle_or_focus_explorer(cx: &mut Context) { +fn open_or_focus_explorer(cx: &mut Context) { cx.callback = Some(Box::new( |compositor: &mut Compositor, cx: &mut compositor::Context| { if let Some(editor) = compositor.find::() { @@ -2236,17 +2236,19 @@ 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() { + (|| match editor.explorer.as_mut() { Some(explore) => explore.content.reveal_current_file(cx), - None => match ui::Explorer::new(cx) { - Ok(explore) => { - let mut explorer = overlayed(explore); - explorer.content.reveal_current_file(cx); - editor.explorer = Some(explorer); - } - Err(err) => cx.editor.set_error(format!("{}", err)), - }, - } + None => { + editor.explorer = Some(overlayed(ui::Explorer::new(cx)?)); + let explorer = editor.explorer.as_mut().unwrap(); + explorer.content.reveal_current_file(cx)?; + explorer.content.focus(); + Ok(()) + } + })() + .unwrap_or_else(|err| { + cx.editor.set_error(err.to_string()) + }) } }, )); diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index cc00e30d..ecce1a5c 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -264,8 +264,8 @@ pub fn default() -> HashMap { "r" => rename_symbol, "h" => select_references_to_symbol_under_cursor, "?" => command_palette, - "e" => toggle_or_focus_explorer, - "E" => reveal_current_file, + "e" => reveal_current_file, + "E" => open_or_focus_explorer, }, "z" => { "View" "z" | "c" => align_view_center, diff --git a/helix-term/src/ui/explore.rs b/helix-term/src/ui/explore.rs index 2c1e7af7..375a6d3b 100644 --- a/helix-term/src/ui/explore.rs +++ b/helix-term/src/ui/explore.rs @@ -1,6 +1,6 @@ use super::{Prompt, TreeItem, TreeOp, TreeView}; use crate::{ - compositor::{Component, Compositor, Context, EventResult}, + compositor::{Component, Context, EventResult}, ctrl, key, shift, ui, }; use anyhow::{bail, ensure, Result}; @@ -148,6 +148,7 @@ struct State { open: bool, current_root: PathBuf, area_width: u16, + filter: String, } impl State { @@ -157,6 +158,7 @@ impl State { current_root, open: true, area_width: 0, + filter: "".to_string(), } } } @@ -215,7 +217,7 @@ impl Explorer { } } - fn reveal_file(&mut self, cx: &mut Context, path: PathBuf) { + fn reveal_file(&mut self, path: PathBuf) -> Result<()> { let current_root = &self.state.current_root; let current_path = path.as_path().to_string_lossy().to_string(); let current_root = current_root.as_path().to_string_lossy().to_string() + "/"; @@ -230,19 +232,16 @@ impl Explorer { ) .split(std::path::MAIN_SEPARATOR) .collect::>(); - match self.tree.reveal_item(segments) { - Ok(_) => { - self.focus(); - } - Err(error) => cx.editor.set_error(error.to_string()), - } + self.tree.reveal_item(segments)?; + self.focus(); + Ok(()) } - pub fn reveal_current_file(&mut self, cx: &mut Context) { + pub fn reveal_current_file(&mut self, cx: &mut Context) -> Result<()> { let current_document_path = doc!(cx.editor).path().cloned(); match current_document_path { - None => cx.editor.set_error("No opened document."), - Some(current_path) => self.reveal_file(cx, current_path), + None => Err(anyhow::anyhow!("No opened document.")), + Some(current_path) => self.reveal_file(current_path), } } @@ -457,11 +456,7 @@ impl Explorer { )); } - fn toggle_current( - item: &mut FileInfo, - cx: &mut Context, - state: &mut State, - ) -> TreeOp { + fn toggle_current(item: &mut FileInfo, cx: &mut Context, state: &mut State) -> TreeOp { if item.path == Path::new("") { return TreeOp::Noop; } @@ -629,13 +624,13 @@ impl Explorer { } key!(Enter) => { if let EventResult::Consumed(_) = prompt.handle_event(Event::Key(event), cx) { - self.tree.filter(prompt.line(), cx, &mut self.state); + self.tree.filter(prompt.line()); } } key!(Esc) | ctrl!('c') => self.tree.restore_recycle(), _ => { if let EventResult::Consumed(_) = prompt.handle_event(Event::Key(event), cx) { - self.tree.filter(prompt.line(), cx, &mut self.state); + self.tree.filter(prompt.line()); } self.prompt = Some((action, prompt)); } @@ -658,20 +653,16 @@ impl Explorer { key!(Enter) => { let search_str = prompt.line().clone(); if !search_str.is_empty() { - self.repeat_motion = Some(Box::new(move |explorer, action, cx| { + self.repeat_motion = Some(Box::new(move |explorer, action, _| { if let PromptAction::Search { search_next: is_next, } = action { explorer.tree.save_view(); if is_next == search_next { - explorer - .tree - .search_next(cx, &search_str, &mut explorer.state); + explorer.tree.search_next(&search_str); } else { - explorer - .tree - .search_previous(cx, &search_str, &mut explorer.state); + explorer.tree.search_previous(&search_str); } } })) @@ -686,10 +677,9 @@ impl Explorer { _ => { if let EventResult::Consumed(_) = prompt.handle_event(Event::Key(event), cx) { if search_next { - self.tree.search_next(cx, prompt.line(), &mut self.state); + self.tree.search_next(prompt.line()); } else { - self.tree - .search_previous(cx, prompt.line(), &mut self.state); + self.tree.search_previous(prompt.line()); } } self.prompt = Some((action, prompt)); @@ -704,71 +694,67 @@ impl Explorer { Some((PromptAction::Filter, _)) => return self.handle_filter_event(event, cx), _ => {} }; - let (action, mut prompt) = match self.prompt.take() { - Some((action, p)) => (action, p), - _ => return EventResult::Ignored(None), - }; - let line = prompt.line(); - match (&action, event.into()) { - ( - 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, - 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)) => { - 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(); + fn handle_prompt_event( + explorer: &mut Explorer, + event: KeyEvent, + cx: &mut Context, + ) -> Result { + let (action, mut prompt) = match explorer.prompt.take() { + Some((action, p)) => (action, p), + _ => return Ok(EventResult::Ignored(None)), + }; + let line = prompt.line(); + match (&action, event.into()) { + ( + PromptAction::CreateFolder { + folder_path, + parent_index, + }, + key!(Enter), + ) => explorer.new_path(folder_path.clone(), line, true, *parent_index)?, + ( + PromptAction::CreateFile { + folder_path, + parent_index, + }, + key!(Enter), + ) => explorer.new_path(folder_path.clone(), line, false, *parent_index)?, + (PromptAction::RemoveDir, key!(Enter)) => { + if line == "y" { + let item = explorer.tree.current_item(); + std::fs::remove_dir_all(&item.path)?; + explorer.tree.fold_current_child(); + explorer.tree.remove_current(); } } - } - (PromptAction::RemoveFile, key!(Enter)) => { - 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}")); - } else { - self.tree.remove_current(); + (PromptAction::RemoveFile, key!(Enter)) => { + if line == "y" { + let item = explorer.tree.current_item(); + std::fs::remove_file(&item.path).map_err(anyhow::Error::from)?; + explorer.tree.remove_current(); } } - } - (PromptAction::RenameFile, key!(Enter)) => { - let item = self.tree.current_item(); - if let Err(e) = std::fs::rename(&item.path, line) { - cx.editor.set_error(format!("{e}")); - } else { - self.tree.remove_current(); - self.reveal_file(cx, PathBuf::from(line)) + (PromptAction::RenameFile, key!(Enter)) => { + let item = explorer.tree.current_item(); + std::fs::rename(&item.path, line)?; + explorer.tree.remove_current(); + explorer.reveal_file(PathBuf::from(line))?; + } + (_, key!(Esc) | ctrl!('c')) => {} + _ => { + prompt.handle_event(Event::Key(event), cx); + explorer.prompt = Some((action, prompt)); } } - (_, key!(Esc) | ctrl!('c')) => {} - _ => { - prompt.handle_event(Event::Key(event), cx); - self.prompt = Some((action, prompt)); + Ok(EventResult::Consumed(None)) + } + match handle_prompt_event(self, event, cx) { + Ok(event_result) => event_result, + Err(err) => { + cx.editor.set_error(err.to_string()); + EventResult::Consumed(None) } } - EventResult::Consumed(None) } fn new_path( diff --git a/helix-term/src/ui/tree.rs b/helix-term/src/ui/tree.rs index 19bfc4d4..7763fb63 100644 --- a/helix-term/src/ui/tree.rs +++ b/helix-term/src/ui/tree.rs @@ -1,4 +1,4 @@ -use std::{cmp::Ordering, path::PathBuf}; +use std::cmp::Ordering; use anyhow::Result; use helix_view::theme::Modifier; @@ -7,12 +7,12 @@ use crate::{ compositor::{Context, EventResult}, ctrl, key, shift, }; -use helix_core::{movement::Direction, unicode::width::UnicodeWidthStr}; +use helix_core::movement::Direction; use helix_view::{ graphics::Rect, input::{Event, KeyEvent}, }; -use tui::{buffer::Buffer as Surface, text::Spans}; +use tui::buffer::Buffer as Surface; pub trait TreeItem: Sized { type Params; @@ -54,12 +54,9 @@ fn vec_to_tree(mut items: Vec) -> Vec> { ) } -pub enum TreeOp { +pub enum TreeOp { Noop, - Restore, - InsertChild(Vec), GetChildsAndInsert, - ReplaceTree { root: T, children: Vec }, } #[derive(Debug, PartialEq, Eq)] @@ -132,7 +129,7 @@ impl<'a, T> DoubleEndedIterator for TreeIter<'a, T> { impl<'a, T> ExactSizeIterator for TreeIter<'a, T> {} -impl Tree { +impl Tree { pub fn filter

(tree: &Tree, predicate: &P) -> Option> where P: Fn(&T) -> bool, @@ -142,7 +139,7 @@ impl Tree { .iter() .filter_map(|tree| Self::filter(tree, predicate)) .collect::>(); - if predicate(&tree.item) || !children.is_empty() { + if tree.item.is_parent() || predicate(&tree.item) || !children.is_empty() { let mut tree = Tree { item: tree.item.clone(), parent_index: tree.parent_index, @@ -156,14 +153,6 @@ impl Tree { None } } - - pub fn parent_index(&self) -> Option { - self.parent_index - } - - pub fn index(&self) -> usize { - self.index - } } impl Tree { @@ -305,6 +294,14 @@ impl Tree { .collect(); self.regenerate_index() } + + pub fn parent_index(&self) -> Option { + self.parent_index + } + + pub fn index(&self) -> usize { + self.index + } } pub struct TreeView { @@ -327,8 +324,7 @@ pub struct TreeView { #[allow(clippy::type_complexity)] pre_render: Option>, #[allow(clippy::type_complexity)] - on_opened_fn: - Option TreeOp + 'static>>, + on_opened_fn: Option TreeOp + 'static>>, #[allow(clippy::type_complexity)] on_folded_fn: Option>, #[allow(clippy::type_complexity)] @@ -355,21 +351,13 @@ impl TreeView { } } - pub fn replace_with_new_items(&mut self, items: Vec) { - todo!() - // let old = std::mem::replace(self, Self::new(vec_to_tree(items))); - // self.on_opened_fn = old.on_opened_fn; - // self.on_folded_fn = old.on_folded_fn; - // self.tree_symbol_style = old.tree_symbol_style; - } - pub fn build_tree(root: T, items: Vec) -> Self { Self::new(root, vec_to_tree(items)) } pub fn with_enter_fn(mut self, f: F) -> Self where - F: FnMut(&mut T, &mut Context, &mut T::Params) -> TreeOp + 'static, + F: FnMut(&mut T, &mut Context, &mut T::Params) -> TreeOp + 'static, { self.on_opened_fn = Some(Box::new(f)); self @@ -464,7 +452,7 @@ impl TreeView { } } - fn go_to_children(&mut self, cx: &mut Context) -> Result<()> { + fn go_to_children(&mut self) -> Result<()> { let current = self.current_mut(); if current.is_opened { self.selected += 1; @@ -530,18 +518,6 @@ impl TreeView { let mut f = || { let current = &mut self.get_mut(selected_index); match on_open_fn(&mut current.item, cx, params) { - TreeOp::Restore => { - panic!(); - // let inserts = std::mem::take(&mut current.folded); - // let _: Vec<_> = self - // .items - // .splice(selected_index + 1..selected_index + 1, inserts) - // .collect(); - return; - } - TreeOp::InsertChild(items) => { - items; - } TreeOp::GetChildsAndInsert => { let items = match current.item.get_children() { Ok(items) => items, @@ -550,33 +526,12 @@ impl TreeView { current.is_opened = true; current.children = vec_to_tree(items); } - TreeOp::ReplaceTree { root, children } => { - self.tree = Tree::new(root, vec_to_tree(children)); - self.selected = 0; - self.winline = 0; - } TreeOp::Noop => {} }; - - // current.folded = vec![]; - // let inserts = vec_to_tree(items, current.level + 1); - // let _: Vec<_> = self - // .items - // .splice(selected_index + 1..selected_index + 1, inserts) - // .collect(); }; f(); self.regenerate_index(); self.on_opened_fn = Some(on_open_fn) - } else { - panic!(); - self.get_mut(selected_index).children = vec![]; - // let current = &mut self.items[selected_index]; - // let inserts = std::mem::take(&mut current.folded); - // let _: Vec<_> = self - // .items - // .splice(selected_index + 1..selected_index + 1, inserts) - // .collect(); } } @@ -589,7 +544,7 @@ impl TreeView { } } - pub fn search_next(&mut self, cx: &mut Context, s: &str, params: &mut T::Params) { + pub fn search_next(&mut self, s: &str) { let skip = std::cmp::max(2, self.save_view.0 + 1); self.selected = self .tree @@ -599,7 +554,7 @@ impl TreeView { self.winline = (self.save_view.1 + self.selected).saturating_sub(self.save_view.0); } - pub fn search_previous(&mut self, cx: &mut Context, s: &str, params: &mut T::Params) { + pub fn search_previous(&mut self, s: &str) { let take = self.save_view.0; self.selected = self .tree @@ -881,69 +836,6 @@ impl TreeView { }, ); } - // let mut text = elem.item.text(cx, skip + index == self.selected, params); - // for (index, elem) in iter { - // let row = index as u16; - // let mut area = Rect::new(area.x, area.y + row, area.width, 1); - // let indent = if elem.level > 0 { - // if index + skip != last_item_index { - // format!("{}├─", "│ ".repeat(elem.level - 1)) - // } else { - // format!("└─{}", "┴─".repeat(elem.level - 1)) - // } - // } else { - // "".to_string() - // }; - - // let indent_len = indent.chars().count(); - // if indent_len > self.col { - // let indent: String = indent.chars().skip(self.col).collect(); - // if !indent.is_empty() { - // surface.set_stringn(area.x, area.y, &indent, area.width as usize, style); - // area = area.clip_left(indent.width() as u16); - // } - // }; - // let mut start_index = self.col.saturating_sub(indent_len); - // let mut text = elem.item.text(cx, skip + index == self.selected, params); - // self.max_len = self.max_len.max(text.width() + indent.len()); - // for span in text.0.iter_mut() { - // if area.width == 0 { - // return; - // } - // if start_index == 0 { - // surface.set_span(area.x, area.y, span, area.width); - // area = area.clip_left(span.width() as u16); - // } else { - // let span_width = span.width(); - // if start_index > span_width { - // start_index -= span_width; - // } else { - // let content: String = span - // .content - // .chars() - // .filter(|c| { - // if start_index > 0 { - // start_index = start_index.saturating_sub(c.to_string().width()); - // false - // } else { - // true - // } - // }) - // .collect(); - // surface.set_string_truncated( - // area.x, - // area.y, - // &content, - // area.width as usize, - // |_| span.style, - // false, - // false, - // ); - // start_index = 0 - // } - // } - // } - // } } pub fn handle_event( @@ -976,7 +868,7 @@ impl TreeView { })); } key!('h') => self.go_to_parent(), - key!('l') => match self.go_to_children(cx) { + key!('l') => match self.go_to_children() { Ok(_) => {} Err(err) => cx.editor.set_error(err.to_string()), }, @@ -998,7 +890,7 @@ impl TreeView { } impl TreeView { - pub fn filter(&mut self, s: &str, cx: &mut Context, params: &mut T::Params) { + pub fn filter(&mut self, s: &str) { if s.is_empty() { self.restore_recycle(); return; @@ -1079,7 +971,9 @@ fn index_elems(parent_index: usize, elems: Vec>) -> Vec> { mod test_tree { use helix_core::movement::Direction; - use super::{index_elems, Tree}; + use crate::ui::TreeItem; + + use super::Tree; #[test] fn test_indexs_elems() { @@ -1232,41 +1126,91 @@ mod test_tree { #[test] fn test_filter() { + #[derive(Clone, Debug, PartialEq, Eq)] + struct MyItem<'a>(bool, &'a str); + impl<'a> TreeItem for MyItem<'a> { + type Params = (); + fn name(&self) -> String { + self.0.to_string() + } + fn is_child(&self, _: &Self) -> bool { + !self.0 + } + fn is_parent(&self) -> bool { + self.0 + } + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.1.cmp(other.1) + } + } let tree = Tree::new( - ".cargo", + MyItem(false, ".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::new( + MyItem(true, "spam"), + vec![Tree::new(MyItem(false, "Cargo.toml"), vec![])], + ), + Tree::new( + MyItem(true, "Cargo.toml"), + vec![Tree::new(MyItem(false, "pam"), vec![])], + ), + Tree::new(MyItem(false, "hello"), vec![]), ], ); - let result = Tree::filter(&tree, &|item| item.to_lowercase().contains("cargo")); + let result = Tree::filter(&tree, &|item| item.1.to_lowercase().contains("cargo")); assert_eq!( result, Some(Tree::new( - ".cargo", + MyItem(false, ".cargo"), vec![ - Tree::new("spam", vec![Tree::new("Cargo.toml", vec![])]), - Tree::new("Cargo.toml", vec![]), + Tree::new( + MyItem(true, "spam"), + vec![Tree::new(MyItem(false, "Cargo.toml"), vec![])] + ), + Tree { + is_opened: true, + ..Tree::new(MyItem(true, "Cargo.toml"), vec![]) + }, ], )) ); - let result = Tree::filter(&tree, &|item| item.to_lowercase().contains("pam")); + let result = Tree::filter(&tree, &|item| item.1.to_lowercase().contains("pam")); assert_eq!( result, Some(Tree::new( - ".cargo", + MyItem(false, ".cargo"), vec![ - Tree::new("spam", vec![]), - Tree::new("Cargo.toml", vec![Tree::new("pam", vec![])]), + Tree { + is_opened: true, + ..Tree::new(MyItem(true, "spam"), vec![]) + }, + Tree::new( + MyItem(true, "Cargo.toml"), + vec![Tree::new(MyItem(false, "pam"), vec![])] + ), ], )) ); - let result = Tree::filter(&tree, &|item| item.to_lowercase().contains("helix")); - assert_eq!(result, None) + let result = Tree::filter(&tree, &|item| item.1.to_lowercase().contains("helix")); + assert_eq!( + result, + Some(Tree::new( + MyItem(false, ".cargo"), + vec![ + Tree { + is_opened: true, + ..Tree::new(MyItem(true, "spam"), vec![]) + }, + Tree { + is_opened: true, + ..Tree::new(MyItem(true, "Cargo.toml"), vec![]) + } + ], + )) + ) } #[test]