feat(explore): add folder/file

pull/9/head
wongjiahau 2 years ago
parent 0f8b641a5d
commit 458fa1ca58

@ -175,7 +175,7 @@ impl TreeItem for FileInfo {
Ok(ret) Ok(ret)
} }
fn text_string(&self) -> String { fn name(&self) -> String {
self.get_text().to_string() self.get_text().to_string()
} }
@ -220,11 +220,11 @@ impl TreeItem for FileInfo {
// } // }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Debug)]
enum PromptAction { enum PromptAction {
Search(bool), // search next/search pre Search(bool), // search next/search pre
Mkdir, CreateFolder { folder_path: PathBuf },
CreateFile, CreateFile { folder_path: PathBuf },
RemoveDir, RemoveDir,
RemoveFile, RemoveFile,
Filter, Filter,
@ -258,10 +258,8 @@ pub struct Explorer {
impl Explorer { impl Explorer {
pub fn new(cx: &mut Context) -> Result<Self> { pub fn new(cx: &mut Context) -> Result<Self> {
let current_root = std::env::current_dir().unwrap_or_else(|_| "./".into()); 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 { 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), state: State::new(true, current_root),
repeat_motion: None, repeat_motion: None,
prompt: None, prompt: None,
@ -269,6 +267,22 @@ impl Explorer {
}) })
} }
fn new_tree(root: PathBuf) -> Result<TreeView<FileInfo>> {
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) { pub fn reveal_current_file(&mut self, cx: &mut Context) {
let current_document_path = doc!(cx.editor).path().cloned(); let current_document_path = doc!(cx.editor).path().cloned();
match current_document_path { 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(( self.prompt = Some((
PromptAction::Mkdir, PromptAction::CreateFolder {
Prompt::new("mkdir: ".into(), None, ui::completers::none, |_, _, _| {}), 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(( self.prompt = Some((
PromptAction::CreateFile, PromptAction::CreateFile {
folder_path: folder_path.clone(),
},
Prompt::new( Prompt::new(
"create file: ".into(), format!(" New file: {}/", folder_path.to_string_lossy()).into(),
None, None,
ui::completers::none, ui::completers::none,
|_, _, _| {}, |_, _, _| {},
), ),
)); ));
Ok(())
}
fn current_parent_folder_path(&self) -> Result<PathBuf> {
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) { fn new_remove_file_prompt(&mut self, cx: &mut Context) {
@ -696,14 +737,14 @@ impl Explorer {
_ => return EventResult::Ignored(None), _ => return EventResult::Ignored(None),
}; };
let line = prompt.line(); let line = prompt.line();
match (action, event.into()) { match (&action, event.into()) {
(PromptAction::Mkdir, key!(Enter)) => { (PromptAction::CreateFolder { folder_path }, key!(Enter)) => {
if let Err(e) = self.new_path(line, true) { if let Err(e) = self.new_path(folder_path.clone(), line, true) {
cx.editor.set_error(format!("{e}")) cx.editor.set_error(format!("{e}"))
} }
} }
(PromptAction::CreateFile, key!(Enter)) => { (PromptAction::CreateFile { folder_path }, key!(Enter)) => {
if let Err(e) = self.new_path(line, false) { if let Err(e) = self.new_path(folder_path.clone(), line, false) {
cx.editor.set_error(format!("{e}")) cx.editor.set_error(format!("{e}"))
} }
} }
@ -735,23 +776,15 @@ impl Explorer {
EventResult::Consumed(None) 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 = self.tree.current_item();
let current_parent = if current.file_type == FileType::Placeholder {
&current.path
} else {
current
.path
.parent()
.ok_or_else(|| anyhow::anyhow!("can not get parent dir"))?
};
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() {
Some(p) if p == current_parent => {} Some(p) if p == current_parent => {}
_ => bail!("The file name is not illegal"), _ => bail!("The file name is not illegal"),
}; };
let f = if is_dir { let file = if is_dir {
std::fs::create_dir(&p)?; std::fs::create_dir(&p)?;
FileInfo::new(p, FileType::Dir) FileInfo::new(p, FileType::Dir)
} else { } else {
@ -760,9 +793,9 @@ impl Explorer {
FileInfo::new(p, FileType::File) FileInfo::new(p, FileType::File)
}; };
if current.file_type == FileType::Placeholder { if current.file_type == FileType::Placeholder {
self.tree.replace_current(f); self.tree.replace_current(file);
} else { } else {
self.tree.insert_current_level(f); self.tree.add_sibling_to_current_item(file)?;
} }
Ok(()) Ok(())
} }
@ -808,33 +841,25 @@ impl Component for Explorer {
self.repeat_motion = Some(repeat_motion); self.repeat_motion = Some(repeat_motion);
} }
} }
// key!('b') => { key!(Backspace) => {
// if let Some(p) = self.state.current_root.parent() { if let Some(parent) = self.state.current_root.parent().clone() {
// match Self::get_items(p.to_path_buf(), cx) { self.change_root(cx, parent.to_path_buf())
// 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!('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),
key!('m') => { key!('a') => {
self.on_next_key = Some(Box::new(|_, explorer, event| { if let Err(error) = self.new_create_file_prompt() {
match event.into() { cx.editor.set_error(error.to_string())
key!('d') => explorer.new_mkdir_prompt(), }
key!('f') => explorer.new_create_file_prompt(), }
_ => return EventResult::Ignored(None), shift!('A') => {
}; if let Err(error) = self.new_create_folder_prompt() {
EventResult::Consumed(None) cx.editor.set_error(error.to_string())
})); }
} }
key!('o') => self.change_root(cx, self.tree.current_item().path.clone()),
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() {

@ -18,15 +18,13 @@ pub trait TreeItem: Sized {
type Params; type Params;
// fn text(&self, cx: &mut Context, selected: bool, params: &mut Self::Params) -> Spans; // 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_child(&self, other: &Self) -> bool;
fn is_parent(&self) -> bool; fn is_parent(&self) -> bool;
fn cmp(&self, other: &Self) -> Ordering; fn cmp(&self, other: &Self) -> Ordering;
fn filter(&self, s: &str) -> bool { fn filter(&self, s: &str) -> bool {
self.text_string() self.name().to_lowercase().contains(&s.to_lowercase())
.to_lowercase()
.contains(&s.to_lowercase())
} }
fn get_children(&self) -> Result<Vec<Self>> { fn get_children(&self) -> Result<Vec<Self>> {
@ -357,7 +355,7 @@ impl<T: TreeItem> TreeView<T> {
match current_tree match current_tree
.children .children
.iter_mut() .iter_mut()
.find(|tree| tree.item.text_string().eq(segment)) .find(|tree| tree.item.name().eq(segment))
{ {
Some(tree) => { Some(tree) => {
if !tree.is_opened { if !tree.is_opened {
@ -387,7 +385,7 @@ impl<T: TreeItem> TreeView<T> {
.fold(&self.tree, |tree, segment| { .fold(&self.tree, |tree, segment| {
tree.children tree.children
.iter() .iter()
.find(|tree| tree.item.text_string().eq(segment)) .find(|tree| tree.item.name().eq(segment))
.expect("Should be unreachable") .expect("Should be unreachable")
}) })
.index; .index;
@ -656,13 +654,34 @@ impl<T: TreeItem> TreeView<T> {
self.selected = selected self.selected = selected
} }
pub fn insert_current_level(&mut self, item: T) { pub fn add_sibling_to_current_item(&mut self, item: T) -> Result<()> {
let current = self.current_mut(); let current = self.current();
current.children.push(Tree::new(item, vec![])); match current.parent_index {
current 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 .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);
// 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<T: TreeItem> TreeView<T> {
selected: selected == tree.index, selected: selected == tree.index,
name: format!( name: format!(
"{}{}", "{}{}",
tree.item.text_string(), tree.item.name(),
if tree.item.is_parent() { if tree.item.is_parent() {
format!("{}", std::path::MAIN_SEPARATOR) format!("{}", std::path::MAIN_SEPARATOR)
} else { } else {
@ -910,9 +929,7 @@ impl<T: TreeItem + Clone> TreeView<T> {
} }
let new_tree = Tree::filter(&self.tree, &|item: &T| { let new_tree = Tree::filter(&self.tree, &|item: &T| {
item.text_string() item.name().to_lowercase().contains(&s.to_lowercase())
.to_lowercase()
.contains(&s.to_lowercase())
}) })
.unwrap_or_else(|| Tree { .unwrap_or_else(|| Tree {
item: self.tree.item.clone(), item: self.tree.item.clone(),

Loading…
Cancel
Save