From 2bafac0c4e077f0328ee700154200e74e4c5a781 Mon Sep 17 00:00:00 2001 From: wongjiahau Date: Mon, 13 Feb 2023 20:55:52 +0800 Subject: [PATCH] feat(explore): go to previous root --- helix-term/src/commands.rs | 4 +- helix-term/src/ui/explore.rs | 250 +++++++++-------------------------- helix-term/src/ui/tree.rs | 8 +- helix-view/src/editor.rs | 15 --- 4 files changed, 70 insertions(+), 207 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index ae725291..c0db885b 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2222,7 +2222,7 @@ fn toggle_or_focus_explorer(cx: &mut Context) { if let Some(editor) = compositor.find::() { match editor.explorer.as_mut() { Some(explore) => explore.content.focus(), - None => match ui::Explorer::new(cx) { + None => match ui::Explorer::new() { Ok(explore) => editor.explorer = Some(overlayed(explore)), Err(err) => cx.editor.set_error(format!("{}", err)), }, @@ -2238,7 +2238,7 @@ fn reveal_current_file(cx: &mut Context) { if let Some(editor) = compositor.find::() { match editor.explorer.as_mut() { Some(explore) => explore.content.reveal_current_file(cx), - None => match ui::Explorer::new(cx) { + None => match ui::Explorer::new() { Ok(explore) => { let mut explorer = overlayed(explore); explorer.content.reveal_current_file(cx); diff --git a/helix-term/src/ui/explore.rs b/helix-term/src/ui/explore.rs index 179ca418..a78a4ea6 100644 --- a/helix-term/src/ui/explore.rs +++ b/helix-term/src/ui/explore.rs @@ -7,7 +7,7 @@ use anyhow::{bail, ensure, Result}; use helix_core::Position; use helix_view::{ editor::{Action, ExplorerPositionEmbed}, - graphics::{CursorKind, Modifier, Rect}, + graphics::{CursorKind, Rect}, input::{Event, KeyEvent}, Editor, }; @@ -16,7 +16,6 @@ use std::cmp::Ordering; use std::path::{Path, PathBuf}; use tui::{ buffer::Buffer as Surface, - text::{Span, Spans}, widgets::{Block, Borders, Widget}, }; @@ -29,10 +28,7 @@ macro_rules! get_theme { #[derive(Debug, Clone, Copy, PartialEq)] enum FileType { File, - Dir, - Exe, - Placeholder, - Parent, + Folder, Root, } @@ -54,20 +50,10 @@ impl FileInfo { } } - fn parent(path: &Path) -> Self { - let p = path.parent().unwrap_or_else(|| Path::new("")); - Self { - file_type: FileType::Parent, - path: p.to_path_buf(), - } - } - fn get_text(&self) -> Cow<'static, str> { match self.file_type { - FileType::Parent => "..".into(), - FileType::Placeholder => "---".into(), FileType::Root => return format!("{}", self.path.display()).into(), - FileType::File | FileType::Exe | FileType::Dir => self + FileType::File | FileType::Folder => self .path .file_name() .map_or("/".into(), |p| p.to_string_lossy().into_owned().into()), @@ -78,65 +64,23 @@ 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; - - // let style = match self.file_type { - // FileType::Parent | FileType::Dir | FileType::Root => "ui.explorer.dir", - // FileType::File | FileType::Exe | FileType::Placeholder => "ui.explorer.file", - // }; - // let style = theme.try_get(style).unwrap_or_else(|| theme.get("ui.text")); - // let style = if !selected { - // style - // } else { - // let patch = match state.focus { - // true => "ui.explorer.focus", - // false => "ui.explorer.unfocus", - // }; - // if let Some(patch) = theme.try_get(patch) { - // style.patch(patch) - // } else { - // style.add_modifier(Modifier::REVERSED) - // } - // }; - // Spans::from(Span::styled(text, style)) - // } - fn is_child(&self, other: &Self) -> bool { - if let FileType::Parent = other.file_type { - return false; - } - if let FileType::Placeholder = self.file_type { - self.path == other.path - } else { - self.path.parent().map_or(false, |p| p == other.path) - } + self.path.parent().map_or(false, |p| p == other.path) } fn cmp(&self, other: &Self) -> Ordering { use FileType::*; match (self.file_type, other.file_type) { - (Parent, _) => return Ordering::Less, - (_, Parent) => return Ordering::Greater, (Root, _) => return Ordering::Less, (_, Root) => return Ordering::Greater, _ => {} }; - if self.path == other.path { - match (self.file_type, other.file_type) { - (_, Placeholder) => return Ordering::Less, - (Placeholder, _) => return Ordering::Greater, - _ => {} - }; - } - if let (Some(p1), Some(p2)) = (self.path.parent(), other.path.parent()) { if p1 == p2 { match (self.file_type, other.file_type) { - (Dir, File | Exe) => return Ordering::Less, - (File | Exe, Dir) => return Ordering::Greater, + (Folder, File) => return Ordering::Less, + (File, Folder) => return Ordering::Greater, _ => {} }; } @@ -146,18 +90,16 @@ impl TreeItem for FileInfo { fn get_children(&self) -> Result> { match self.file_type { - FileType::Root | FileType::Dir => {} + FileType::Root | FileType::Folder => {} _ => return Ok(vec![]), }; - let mut ret: Vec<_> = std::fs::read_dir(&self.path)? + let ret: Vec<_> = std::fs::read_dir(&self.path)? .filter_map(|entry| entry.ok()) .filter_map(|entry| { entry.metadata().ok().map(|meta| { - let is_exe = false; - let file_type = match (meta.is_dir(), is_exe) { - (true, _) => FileType::Dir, - (_, false) => FileType::File, - (_, true) => FileType::Exe, + let file_type = match meta.is_dir() { + true => FileType::Folder, + false => FileType::File, }; Self { file_type, @@ -166,12 +108,6 @@ impl TreeItem for FileInfo { }) }) .collect(); - if ret.is_empty() { - ret.push(Self { - path: self.path.clone(), - file_type: FileType::Placeholder, - }) - } Ok(ret) } @@ -181,48 +117,17 @@ impl TreeItem for FileInfo { fn is_parent(&self) -> bool { match self.file_type { - FileType::Dir | FileType::Parent | FileType::Root => true, + FileType::Folder | FileType::Root => true, _ => false, } } } -// #[derive(Default, Debug, Clone)] -// struct PathState { -// root: PathBuf, -// sub_items: Vec, -// selected: usize, -// save_view: (usize, usize), // (selected, row) -// row: usize, -// col: usize, -// max_len: usize, -// } - -// impl PathState { - -// fn mkdir(&mut self, dir: &str) -> Result<()> { -// self.new_path(dir, FileType::Dir) -// } - -// fn create_file(&mut self, f: &str) -> Result<()> { -// self.new_path(f, FileType::File) -// } - -// fn remove_current_file(&mut self) -> Result<()> { -// let item = &self.sub_items[self.selected]; -// std::fs::remove_file(item.path_with_root(&self.root))?; -// self.sub_items.remove(self.selected); -// if self.selected >= self.sub_items.len() { -// self.selected = self.sub_items.len() - 1; -// } -// Ok(()) -// } - -// } - #[derive(Clone, Debug)] enum PromptAction { - Search(bool), // search next/search pre + Search { + search_next: bool, + }, // search next/search pre CreateFolder { folder_path: PathBuf, parent_index: usize, @@ -254,6 +159,7 @@ impl State { pub struct Explorer { tree: TreeView, + history: Vec>, show_help: bool, state: State, prompt: Option<(PromptAction, Prompt)>, @@ -264,10 +170,11 @@ pub struct Explorer { } impl Explorer { - pub fn new(cx: &mut Context) -> Result { + pub fn new() -> Result { let current_root = std::env::current_dir().unwrap_or_else(|_| "./".into()); Ok(Self { - tree: Self::new_tree(current_root.clone())?, + tree: Self::new_tree_view(current_root.clone())?, + history: vec![], show_help: false, state: State::new(true, current_root), repeat_motion: None, @@ -276,17 +183,27 @@ impl Explorer { }) } - fn new_tree(root: PathBuf) -> Result> { + fn new_tree_view(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 push_history(&mut self, tree_view: TreeView) { + self.history.push(tree_view); + const MAX_HISTORY_SIZE: usize = 20; + Vec::truncate(&mut self.history, MAX_HISTORY_SIZE) + } + fn change_root(&mut self, cx: &mut Context, root: PathBuf) { - match Self::new_tree(root.clone()) { + if self.state.current_root.eq(&root) { + return; + } + match Self::new_tree_view(root.clone()) { Ok(tree) => { + let old_tree = std::mem::replace(&mut self.tree, tree); + self.push_history(old_tree); self.state.current_root = root; - self.tree = tree; } Err(e) => cx.editor.set_error(format!("{e}")), } @@ -323,15 +240,6 @@ impl Explorer { } } - // pub fn new_with_uri(uri: String) -> Result { - // // support remote file? - - // let p = Path::new(&uri); - // ensure!(p.exists(), "path: {uri} is not exist"); - // ensure!(p.is_dir(), "path: {uri} is not dir"); - // Ok(Self::default().with_list(get_sub(p, None)?)) - // } - pub fn focus(&mut self) { self.state.focus = true; } @@ -344,24 +252,11 @@ impl Explorer { self.state.focus } - fn get_items(p: PathBuf, cx: &mut Context) -> Result> { - todo!() - // let mut items = vec![FileInfo::parent(p.as_path())]; - - // if cx.editor.config().explorer.is_tree() { - // items.push(root) - // } - // Ok(items) - } - fn render_preview(&mut self, area: Rect, surface: &mut Surface, editor: &Editor) { if area.height <= 2 || area.width < 60 { return; } let item = self.tree.current().item(); - if item.file_type == FileType::Placeholder { - return; - } let head_area = render_block(area.clip_bottom(area.height - 2), surface, Borders::BOTTOM); let path_str = format!("{}", item.path.display()); surface.set_stringn( @@ -387,8 +282,9 @@ impl Explorer { "d Delete file", "/ Search", "f Filter", - "[ Change root to parent", - "] Change root to current", + "[ Change root to parent folder", + "] Change root to current folder", + "^o Go to previous root", "R Refresh tree", ] .into_iter() @@ -413,8 +309,8 @@ impl Explorer { fn new_search_prompt(&mut self, search_next: bool) { self.tree.save_view(); self.prompt = Some(( - PromptAction::Search(search_next), - Prompt::new("search: ".into(), None, ui::completers::none, |_, _, _| {}), + PromptAction::Search { search_next }, + Prompt::new(" Search: ".into(), None, ui::completers::none, |_, _, _| {}), )) } @@ -422,7 +318,7 @@ impl Explorer { self.tree.save_view(); self.prompt = Some(( PromptAction::Filter, - Prompt::new("filter: ".into(), None, ui::completers::none, |_, _, _| {}), + Prompt::new(" Filter: ".into(), None, ui::completers::none, |_, _, _| {}), )) } @@ -484,10 +380,8 @@ impl Explorer { 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::Folder => self.new_remove_dir_prompt(cx), + FileType::File => self.new_remove_file_prompt(cx), FileType::Root => cx.editor.set_error("Root is not removable"), } } @@ -509,11 +403,6 @@ impl Explorer { fn new_remove_file_prompt(&mut self, cx: &mut Context) { let item = self.tree.current_item(); let check = || { - ensure!(item.file_type != FileType::Placeholder, "The path is empty"); - ensure!( - item.file_type != FileType::Parent, - "can not remove parent dir" - ); ensure!(item.path.is_file(), "The path is not a file"); let doc = cx.editor.document_by_path(&item.path); ensure!(doc.is_none(), "The file is opened"); @@ -533,11 +422,6 @@ impl Explorer { fn new_remove_dir_prompt(&mut self, cx: &mut Context) { let item = self.tree.current_item(); let check = || { - ensure!(item.file_type != FileType::Placeholder, "The path is empty"); - ensure!( - item.file_type != FileType::Parent, - "can not remove parent dir" - ); ensure!(item.path.is_dir(), "The path is not a dir"); let doc = cx.editor.documents().find(|doc| { doc.path() @@ -563,9 +447,6 @@ impl Explorer { cx: &mut Context, state: &mut State, ) -> TreeOp { - if item.file_type == FileType::Placeholder { - return TreeOp::Noop; - } if item.path == Path::new("") { return TreeOp::Noop; } @@ -585,17 +466,7 @@ impl Explorer { } if item.path.is_dir() { - if cx.editor.config().explorer.is_list() || item.file_type == FileType::Parent { - match Self::get_items(item.path.clone(), cx) { - Ok(items) => { - state.current_root = item.path.clone(); - return TreeOp::ReplaceTree(items); - } - Err(e) => cx.editor.set_error(format!("{e}")), - } - } else { - return TreeOp::GetChildsAndInsert; - } + return TreeOp::GetChildsAndInsert; } cx.editor.set_error("unkonw file type"); TreeOp::Noop @@ -758,7 +629,7 @@ impl Explorer { fn handle_search_event(&mut self, event: KeyEvent, cx: &mut Context) -> EventResult { let (action, mut prompt) = self.prompt.take().unwrap(); let search_next = match action { - PromptAction::Search(search_next) => search_next, + PromptAction::Search { search_next } => search_next, _ => return EventResult::Ignored(None), }; match event.into() { @@ -771,7 +642,10 @@ impl Explorer { let search_str = prompt.line().clone(); if !search_str.is_empty() { self.repeat_motion = Some(Box::new(move |explorer, action, cx| { - if let PromptAction::Search(is_next) = action { + if let PromptAction::Search { + search_next: is_next, + } = action + { explorer.tree.save_view(); if is_next == search_next { explorer @@ -809,7 +683,7 @@ impl Explorer { fn handle_prompt_event(&mut self, event: KeyEvent, cx: &mut Context) -> EventResult { match &self.prompt { - Some((PromptAction::Search(_), _)) => return self.handle_search_event(event, cx), + Some((PromptAction::Search { .. }, _)) => return self.handle_search_event(event, cx), Some((PromptAction::Filter, _)) => return self.handle_filter_event(event, cx), _ => {} }; @@ -887,32 +761,33 @@ impl Explorer { 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() { + let path = helix_core::path::get_normalized_path(¤t_parent.join(file_name)); + match path.parent() { Some(p) if p == current_parent => {} _ => bail!("The file name is not illegal"), }; let file = if is_dir { - std::fs::create_dir(&p)?; - FileInfo::new(p, FileType::Dir) + std::fs::create_dir(&path)?; + FileInfo::new(path, FileType::Folder) } else { let mut fd = std::fs::OpenOptions::new(); - fd.create_new(true).write(true).open(&p)?; - FileInfo::new(p, FileType::File) + fd.create_new(true).write(true).open(&path)?; + FileInfo::new(path, FileType::File) }; - if current.file_type == FileType::Placeholder { - self.tree.replace_current(file); - } else { - self.tree.add_child(parent_index, file)?; - } + self.tree.add_child(parent_index, file)?; Ok(()) } fn toggle_help(&mut self) { self.show_help = !self.show_help } + + fn go_to_previous_root(&mut self) { + if let Some(tree) = self.history.pop() { + self.tree = tree + } + } } impl Component for Explorer { @@ -945,13 +820,13 @@ impl Component for Explorer { ctrl!('c') => return close_fn, key!('n') => { if let Some(mut repeat_motion) = self.repeat_motion.take() { - repeat_motion(self, PromptAction::Search(true), cx); + repeat_motion(self, PromptAction::Search { search_next: true }, cx); self.repeat_motion = Some(repeat_motion); } } shift!('N') => { if let Some(mut repeat_motion) = self.repeat_motion.take() { - repeat_motion(self, PromptAction::Search(false), cx); + repeat_motion(self, PromptAction::Search { search_next: false }, cx); self.repeat_motion = Some(repeat_motion); } } @@ -974,6 +849,7 @@ impl Component for Explorer { } } key!(']') => self.change_root(cx, self.tree.current_item().path.clone()), + ctrl!('o') => self.go_to_previous_root(), key!('d') => self.new_remove_prompt(cx), key!('r') => self.new_rename_prompt(), shift!('R') => { diff --git a/helix-term/src/ui/tree.rs b/helix-term/src/ui/tree.rs index bd5eff7a..19bfc4d4 100644 --- a/helix-term/src/ui/tree.rs +++ b/helix-term/src/ui/tree.rs @@ -59,7 +59,7 @@ pub enum TreeOp { Restore, InsertChild(Vec), GetChildsAndInsert, - ReplaceTree(Vec), + ReplaceTree { root: T, children: Vec }, } #[derive(Debug, PartialEq, Eq)] @@ -550,8 +550,10 @@ impl TreeView { current.is_opened = true; current.children = vec_to_tree(items); } - TreeOp::ReplaceTree(items) => { - return self.replace_with_new_items(items); + TreeOp::ReplaceTree { root, children } => { + self.tree = Tree::new(root, vec_to_tree(children)); + self.selected = 0; + self.winline = 0; } TreeOp::Noop => {} }; diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 4bb386fa..bfb852de 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -132,7 +132,6 @@ pub enum ExplorerPositionEmbed { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case", default, deny_unknown_fields)] pub struct ExplorerConfig { - pub style: ExplorerStyle, pub position: ExplorerPosition, /// explorer column width pub column_width: usize, @@ -154,25 +153,11 @@ impl ExplorerConfig { } } - pub fn is_list(&self) -> bool { - match self.style { - ExplorerStyle::List => true, - ExplorerStyle::Tree => false, - } - } - - pub fn is_tree(&self) -> bool { - match self.style { - ExplorerStyle::List => false, - ExplorerStyle::Tree => true, - } - } } impl Default for ExplorerConfig { fn default() -> Self { Self { - style: ExplorerStyle::Tree, position: ExplorerPosition::Left, column_width: 30, }