- refactor(explore):Move filter to Tree
- feat(explore): Implement mkdir -p (but not tested yet)
- feat(ui/tree): Implement jump backward
- test(ui/tree): Refresh
pull/9/head
wongjiahau 2 years ago
parent 2e654a0775
commit 2e7709e505

@ -27,6 +27,9 @@ TODO
- [x] Remove comments - [x] Remove comments
- [x] fix warnings - [x] fix warnings
- [x] refactor, add tree.expand_children() method - [x] refactor, add tree.expand_children() method
- [] Change '[' to "go to previous root"
- [] Change 'b' to "go to parent"
- [] Use C-o for jumping to previous position
- [] add integration testing (test explorer rendering) - [] add integration testing (test explorer rendering)
- [] search highlight matching word - [] search highlight matching word
- [] Error didn't clear - [] Error didn't clear
@ -37,4 +40,5 @@ TODO
- [] Fix panic bugs (see github comments) - [] Fix panic bugs (see github comments)
- [] Sticky ancestors - [] Sticky ancestors
- [] Ctrl-o should work for 'h', 'gg', 'ge', etc - [] Ctrl-o should work for 'h', 'gg', 'ge', etc
- [] explorer(previow): overflow where bufferline is there
- [] explorer(preview): content not sorted - [] explorer(preview): content not sorted

@ -3,7 +3,7 @@ use crate::{
compositor::{Component, Context, EventResult}, compositor::{Component, Context, EventResult},
ctrl, key, shift, ui, ctrl, key, shift, ui,
}; };
use anyhow::{bail, ensure, Result}; use anyhow::{ensure, Result};
use helix_core::Position; use helix_core::Position;
use helix_view::{ use helix_view::{
editor::{Action, ExplorerPositionEmbed}, editor::{Action, ExplorerPositionEmbed},
@ -122,21 +122,11 @@ impl TreeViewItem for FileInfo {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
enum PromptAction { enum PromptAction {
Search { CreateFolder { folder_path: PathBuf },
search_next: bool, CreateFile { folder_path: PathBuf },
}, // search next/search pre
CreateFolder {
folder_path: PathBuf,
parent_index: usize,
},
CreateFile {
folder_path: PathBuf,
parent_index: usize,
},
RemoveDir, RemoveDir,
RemoveFile(Option<DocumentId>), RemoveFile(Option<DocumentId>),
RenameFile(Option<DocumentId>), RenameFile(Option<DocumentId>),
Filter,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -288,11 +278,9 @@ impl Explorer {
("A", "Add folder"), ("A", "Add folder"),
("r", "Rename file/folder"), ("r", "Rename file/folder"),
("d", "Delete file"), ("d", "Delete file"),
("f", "Filter"), ("b", "Change root to parent folder"),
("[", "Change root to parent folder"),
("]", "Change root to current folder"), ("]", "Change root to current folder"),
("C-o", "Go to previous root"), ("[", "Go to previous root"),
("R", "Refresh"),
("+", "Increase size"), ("+", "Increase size"),
("-", "Decrease size"), ("-", "Decrease size"),
("q", "Close"), ("q", "Close"),
@ -326,28 +314,10 @@ 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, |_, _, _| {}),
))
}
fn new_filter_prompt(&mut self, cx: &mut Context) {
// self.tree.save_view();
self.prompt = Some((
PromptAction::Filter,
Prompt::new(" Filter: ".into(), None, ui::completers::none, |_, _, _| {})
.with_line(self.state.filter.clone(), cx.editor),
))
}
fn new_create_folder_prompt(&mut self) -> Result<()> { fn new_create_folder_prompt(&mut self) -> Result<()> {
let (parent_index, folder_path) = self.nearest_folder()?; let 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(
@ -361,10 +331,9 @@ impl Explorer {
} }
fn new_create_file_prompt(&mut self) -> Result<()> { fn new_create_file_prompt(&mut self) -> Result<()> {
let (parent_index, folder_path) = self.nearest_folder()?; let 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(
@ -377,24 +346,18 @@ impl Explorer {
Ok(()) Ok(())
} }
fn nearest_folder(&self) -> Result<(usize, PathBuf)> { fn nearest_folder(&self) -> Result<PathBuf> {
let current = self.tree.current(); let current = self.tree.current();
if current.item().is_parent() { if current.item().is_parent() {
Ok((current.index(), current.item().path.to_path_buf())) Ok(current.item().path.to_path_buf())
} else { } 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(|| { let parent_path = current.item().path.parent().ok_or_else(|| {
anyhow::anyhow!(format!( anyhow::anyhow!(format!(
"Unable to get parent path of '{}'", "Unable to get parent path of '{}'",
current.item().path.to_string_lossy() current.item().path.to_string_lossy()
)) ))
})?; })?;
Ok((parent_index, parent_path.to_path_buf())) Ok(parent_path.to_path_buf())
} }
} }
@ -522,15 +485,8 @@ impl Explorer {
area.width.into(), area.width.into(),
title_style, title_style,
); );
surface.set_stringn(
area.x,
area.y.saturating_add(1),
format!("[FILTER]: {}", self.state.filter),
area.width.into(),
cx.editor.theme.get("ui.text"),
);
self.tree self.tree
.render(area.clip_top(2), surface, cx, &self.state.filter); .render(area.clip_top(1), surface, cx, &self.state.filter);
} }
pub fn render_embed( pub fn render_embed(
@ -646,60 +602,7 @@ impl Explorer {
EventResult::Consumed(None) EventResult::Consumed(None)
} }
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,
_ => return EventResult::Ignored(None),
};
match event {
key!(Tab) | key!(Down) | ctrl!('j') => {
let filter = self.state.filter.clone();
return self
.tree
.handle_event(&Event::Key(*event), cx, &mut self.state, &filter);
}
key!(Enter) => {
let search_str = prompt.line().clone();
if !search_str.is_empty() {
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(&search_str);
} else {
// explorer.tree.search_previous(&search_str);
}
}
}))
} else {
self.repeat_motion = None;
}
}
// key!(Esc) | ctrl!('c') => self.tree.restore_view(),
_ => {
if let EventResult::Consumed(_) = prompt.handle_event(&Event::Key(*event), cx) {
if search_next {
// self.tree.search_next(prompt.line());
} else {
// self.tree.search_previous(prompt.line());
}
}
self.prompt = Some((action, prompt));
}
};
EventResult::Consumed(None)
}
fn handle_prompt_event(&mut self, event: &KeyEvent, cx: &mut Context) -> EventResult { 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::Filter, _)) => return self.handle_filter_event(event, cx),
_ => {}
};
fn handle_prompt_event( fn handle_prompt_event(
explorer: &mut Explorer, explorer: &mut Explorer,
event: &KeyEvent, event: &KeyEvent,
@ -711,20 +614,12 @@ impl Explorer {
}; };
let line = prompt.line(); let line = prompt.line();
match (&action, event) { match (&action, event) {
( (PromptAction::CreateFolder { folder_path }, key!(Enter)) => {
PromptAction::CreateFolder { explorer.new_path(folder_path.clone(), line, true)?
folder_path, }
parent_index, (PromptAction::CreateFile { folder_path }, key!(Enter)) => {
}, explorer.new_path(folder_path.clone(), line, false)?
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)) => { (PromptAction::RemoveDir, key!(Enter)) => {
if line == "y" { if line == "y" {
let item = explorer.tree.current_item(); let item = explorer.tree.current_item();
@ -768,30 +663,19 @@ impl Explorer {
} }
} }
fn new_path( fn new_path(&mut self, current_parent: PathBuf, file_name: &str, is_dir: bool) -> Result<()> {
&mut self,
current_parent: PathBuf,
file_name: &str,
is_dir: bool,
parent_index: usize,
) -> Result<()> {
let path = helix_core::path::get_normalized_path(&current_parent.join(file_name)); let path = helix_core::path::get_normalized_path(&current_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 { if is_dir {
std::fs::create_dir(&path)?; std::fs::create_dir_all(&path)?;
FileInfo::new(path, FileType::Folder)
} else { } else {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let mut fd = std::fs::OpenOptions::new(); let mut fd = std::fs::OpenOptions::new();
fd.create_new(true).write(true).open(&path)?; fd.create_new(true).write(true).open(&path)?;
FileInfo::new(path, FileType::File)
}; };
self.tree self.reveal_file(path)
.add_child(parent_index, file, &self.state.filter)?;
Ok(())
} }
fn toggle_help(&mut self) { fn toggle_help(&mut self) {
@ -851,7 +735,6 @@ impl Component for Explorer {
match key_event { match key_event {
key!(Esc) => self.unfocus(), key!(Esc) => self.unfocus(),
key!('q') => self.close(), key!('q') => self.close(),
key!('f') => self.new_filter_prompt(cx),
key!('?') => self.toggle_help(), key!('?') => self.toggle_help(),
key!('a') => { key!('a') => {
if let Err(error) = self.new_create_file_prompt() { if let Err(error) = self.new_create_file_prompt() {
@ -863,21 +746,16 @@ impl Component for Explorer {
cx.editor.set_error(error.to_string()) cx.editor.set_error(error.to_string())
} }
} }
key!('[') => { key!('b') => {
if let Some(parent) = self.state.current_root.parent().clone() { if let Some(parent) = self.state.current_root.parent().clone() {
let path = parent.to_path_buf(); let path = parent.to_path_buf();
self.change_root(cx, path) self.change_root(cx, path)
} }
} }
key!(']') => self.change_root(cx, self.tree.current_item().path.clone()), key!(']') => self.change_root(cx, self.tree.current_item().path.clone()),
ctrl!('o') => self.go_to_previous_root(), key!('[') => self.go_to_previous_root(),
key!('d') => self.new_remove_prompt(cx), key!('d') => self.new_remove_prompt(cx),
key!('r') => self.new_rename_prompt(cx), key!('r') => self.new_rename_prompt(cx),
shift!('R') => {
if let Err(error) = self.tree.refresh(&self.state.filter) {
cx.editor.set_error(error.to_string())
}
}
key!('-') => self.decrease_size(), key!('-') => self.decrease_size(),
key!('+') => self.increase_size(), key!('+') => self.increase_size(),
_ => { _ => {

@ -296,12 +296,6 @@ impl<T> Tree<T> {
} }
} }
#[derive(Clone, Debug)]
enum PromptAction {
Search { search_next: bool },
Filter,
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct SavedView { struct SavedView {
selected: usize, selected: usize,
@ -311,13 +305,19 @@ struct SavedView {
pub struct TreeView<T: TreeViewItem> { pub struct TreeView<T: TreeViewItem> {
tree: Tree<T>, tree: Tree<T>,
prompt: Option<(PromptAction, Prompt)>, search_prompt: Option<(Direction, Prompt)>,
filter_prompt: Option<Prompt>,
search_str: String, search_str: String,
filter: String,
/// Selected item idex /// Selected item idex
selected: usize, selected: usize,
history: Vec<usize>,
saved_view: Option<SavedView>, saved_view: Option<SavedView>,
/// For implementing vertical scroll /// For implementing vertical scroll
@ -345,6 +345,7 @@ impl<T: TreeViewItem> TreeView<T> {
Self { Self {
tree: Tree::new(root, items), tree: Tree::new(root, items),
selected: 0, selected: 0,
history: vec![],
saved_view: None, saved_view: None,
winline: 0, winline: 0,
column: 0, column: 0,
@ -355,8 +356,10 @@ impl<T: TreeViewItem> TreeView<T> {
on_opened_fn: None, on_opened_fn: None,
on_folded_fn: None, on_folded_fn: None,
on_next_key: None, on_next_key: None,
prompt: None, search_prompt: None,
search_str: "".to_owned(), filter_prompt: None,
search_str: "".into(),
filter: "".into(),
} }
} }
@ -394,7 +397,7 @@ impl<T: TreeViewItem> 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>, filter: &String) -> Result<()> { pub fn reveal_item(&mut self, segments: Vec<&str>, filter: &String) -> Result<()> {
self.tree.refresh(filter)?; self.refresh(filter)?;
// Expand the tree // Expand the tree
segments.iter().fold( segments.iter().fold(
@ -480,7 +483,9 @@ impl<T: TreeViewItem> TreeView<T> {
} }
pub fn refresh(&mut self, filter: &String) -> Result<()> { pub fn refresh(&mut self, filter: &String) -> Result<()> {
self.tree.refresh(filter) self.tree.refresh(filter)?;
self.set_selected(self.selected);
Ok(())
} }
fn move_to_first(&mut self) { fn move_to_first(&mut self) {
@ -510,8 +515,20 @@ pub fn tree_view_help() -> Vec<(&'static str, &'static str)> {
("k, up", "Up"), ("k, up", "Up"),
("h, left", "Go to parent"), ("h, left", "Go to parent"),
("l, right", "Expand"), ("l, right", "Expand"),
("L", "Scroll right"), ("f", "Filter"),
("/", "Search"),
("n", "Go to next search match"),
("N", "Go to previous search match"),
("R", "Refresh"),
("H", "Scroll left"), ("H", "Scroll left"),
("L", "Scroll right"),
("Home", "Scroll to the leftmost"),
("End", "Scroll to the rightmost"),
("C-o", "Jump backward"),
("C-d", "Half page down"),
("C-u", "Half page up"),
("PageUp", "Full page up"),
("PageDown", "Full page down"),
("zz", "Align view center"), ("zz", "Align view center"),
("zt", "Align view top"), ("zt", "Align view top"),
("zb", "Align view bottom"), ("zb", "Align view bottom"),
@ -519,11 +536,6 @@ pub fn tree_view_help() -> Vec<(&'static str, &'static str)> {
("ge", "Go to last line"), ("ge", "Go to last line"),
("gh", "Go to line start"), ("gh", "Go to line start"),
("gl", "Go to line end"), ("gl", "Go to line end"),
("C-d", "Page down"),
("C-u", "Page up"),
("/", "Search"),
("n", "Go to next search match"),
("N", "Go to previous search match"),
] ]
} }
@ -608,6 +620,15 @@ impl<T: TreeViewItem> TreeView<T> {
} }
fn set_selected(&mut self, selected: usize) { fn set_selected(&mut self, selected: usize) {
let previous_selected = self.selected;
self.set_selected_without_history(selected);
if previous_selected.abs_diff(selected) > 1 {
self.history.push(previous_selected)
}
}
fn set_selected_without_history(&mut self, selected: usize) {
let selected = selected.clamp(0, self.tree.len().saturating_sub(1));
if selected > self.selected { if selected > self.selected {
// Move down // Move down
self.winline = selected.min( self.winline = selected.min(
@ -621,7 +642,13 @@ impl<T: TreeViewItem> TreeView<T> {
.saturating_sub(self.selected.saturating_sub(selected)), .saturating_sub(self.selected.saturating_sub(selected)),
); );
} }
self.selected = selected; self.selected = selected
}
fn jump_backward(&mut self) {
if let Some(index) = self.history.pop() {
self.set_selected_without_history(index);
}
} }
fn move_up(&mut self, rows: usize) { fn move_up(&mut self, rows: usize) {
@ -674,11 +701,15 @@ impl<T: TreeViewItem> TreeView<T> {
} }
fn get(&self, index: usize) -> &Tree<T> { fn get(&self, index: usize) -> &Tree<T> {
self.tree.get(index).unwrap() self.tree
.get(index)
.expect(format!("Tree: index {index} is out of bound").as_str())
} }
fn get_mut(&mut self, index: usize) -> &mut Tree<T> { fn get_mut(&mut self, index: usize) -> &mut Tree<T> {
self.tree.get_mut(index).unwrap() self.tree
.get_mut(index)
.expect(format!("Tree: index {index} is out of bound").as_str())
} }
pub fn current(&self) -> &Tree<T> { pub fn current(&self) -> &Tree<T> {
@ -809,23 +840,38 @@ fn render_tree<T: TreeViewItem>(
impl<T: TreeViewItem + Clone> TreeView<T> { impl<T: TreeViewItem + Clone> TreeView<T> {
pub fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context, filter: &String) { pub fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context, filter: &String) {
let style = cx.editor.theme.get(&self.tree_symbol_style); let style = cx.editor.theme.get(&self.tree_symbol_style);
let prompt_area = area.with_height(1);
if let Some((_, prompt)) = self.prompt.as_mut() { let filter_prompt_area = area.with_height(1);
surface.set_style(prompt_area, style.add_modifier(Modifier::REVERSED)); if let Some(prompt) = self.filter_prompt.as_mut() {
prompt.render_prompt(prompt_area, surface, cx) surface.set_style(filter_prompt_area, style.add_modifier(Modifier::REVERSED));
prompt.render_prompt(filter_prompt_area, surface, cx)
} else {
surface.set_stringn(
filter_prompt_area.x,
filter_prompt_area.y,
format!("[FILTER]: {}", self.filter.clone()),
filter_prompt_area.width as usize,
style,
);
}
let search_prompt_area = area.clip_top(1).with_height(1);
if let Some((_, prompt)) = self.search_prompt.as_mut() {
surface.set_style(search_prompt_area, style.add_modifier(Modifier::REVERSED));
prompt.render_prompt(search_prompt_area, surface, cx)
} else { } else {
surface.set_stringn( surface.set_stringn(
prompt_area.x, search_prompt_area.x,
prompt_area.y, search_prompt_area.y,
format!("[SEARCH]: {}", self.search_str.clone()), format!("[SEARCH]: {}", self.search_str.clone()),
prompt_area.width as usize, search_prompt_area.width as usize,
style, style,
); );
} }
let ancestor_style = cx.editor.theme.get("ui.text.focus"); let ancestor_style = cx.editor.theme.get("ui.text.focus");
let area = area.clip_top(1); let area = area.clip_top(2);
let iter = self.render_lines(area, filter).into_iter().enumerate(); let iter = self.render_lines(area, filter).into_iter().enumerate();
for (index, line) in iter { for (index, line) in iter {
@ -958,9 +1004,14 @@ impl<T: TreeViewItem + Clone> TreeView<T> {
return EventResult::Consumed(None); return EventResult::Consumed(None);
} }
if let EventResult::Consumed(c) = self.handle_prompt_event(key_event, cx) { if let EventResult::Consumed(c) = self.handle_search_event(key_event, cx) {
return EventResult::Consumed(c);
}
if let EventResult::Consumed(c) = self.handle_filter_event(key_event, cx) {
return EventResult::Consumed(c); return EventResult::Consumed(c);
} }
let count = std::mem::replace(&mut self.count, 0); let count = std::mem::replace(&mut self.count, 0);
match key_event { match key_event {
key!(i @ '0'..='9') => self.count = i.to_digit(10).unwrap() as usize + count * 10, key!(i @ '0'..='9') => self.count = i.to_digit(10).unwrap() as usize + count * 10,
@ -993,64 +1044,115 @@ impl<T: TreeViewItem + Clone> TreeView<T> {
_ => {} _ => {}
})); }));
} }
key!('/') => self.new_search_prompt(true), key!('/') => self.new_search_prompt(Direction::Forward),
key!('n') => self.move_to_next_search_match(), key!('n') => self.move_to_next_search_match(),
shift!('N') => self.move_to_previous_next_match(), shift!('N') => self.move_to_previous_next_match(),
key!('f') => self.new_filter_prompt(cx),
key!(PageDown) => self.move_down_page(),
key!(PageUp) => self.move_up_page(),
shift!('R') => {
let filter = self.filter.clone();
if let Err(error) = self.refresh(&filter) {
cx.editor.set_error(error.to_string())
}
}
key!(Home) => self.move_leftmost(),
key!(End) => self.move_rightmost(),
ctrl!('o') => self.jump_backward(),
_ => return EventResult::Ignored(None), _ => return EventResult::Ignored(None),
} }
EventResult::Consumed(None) EventResult::Consumed(None)
} }
fn handle_prompt_event(&mut self, event: &KeyEvent, cx: &mut Context) -> EventResult { fn handle_filter_event(&mut self, event: &KeyEvent, cx: &mut Context) -> EventResult {
match &self.prompt { if let Some(mut prompt) = self.filter_prompt.take() {
Some((PromptAction::Search { .. }, _)) => return self.handle_search_event(event, cx), (|| -> Result<()> {
// Some((PromptAction::Filter, _)) => return self.handle_filter_event(event, cx), match event {
_ => EventResult::Ignored(None), key!(Enter) => {
if let EventResult::Consumed(_) =
prompt.handle_event(&Event::Key(*event), cx)
{
self.refresh(prompt.line())?;
}
}
key!(Esc) | ctrl!('c') => {
self.filter.clear();
self.refresh(&"".to_string())?;
}
_ => {
if let EventResult::Consumed(_) =
prompt.handle_event(&Event::Key(*event), cx)
{
self.refresh(prompt.line())?;
}
self.filter = prompt.line().clone();
self.filter_prompt = Some(prompt);
}
};
Ok(())
})()
.unwrap_or_else(|err| cx.editor.set_error(format!("{err}")));
EventResult::Consumed(None)
} else {
EventResult::Ignored(None)
} }
} }
fn handle_search_event(&mut self, event: &KeyEvent, cx: &mut Context) -> EventResult { fn handle_search_event(&mut self, event: &KeyEvent, cx: &mut Context) -> EventResult {
let (action, mut prompt) = self.prompt.take().unwrap(); if let Some((direction, mut prompt)) = self.search_prompt.take() {
let search_next = match action { match event {
PromptAction::Search { search_next } => search_next, key!(Enter) => {
_ => return EventResult::Ignored(None), self.set_search_str(prompt.line().clone());
}; EventResult::Consumed(None)
match event { }
key!(Enter) => { key!(Esc) => EventResult::Consumed(None),
self.set_search_str(prompt.line().clone()); _ => {
EventResult::Consumed(None) let event = prompt.handle_event(&Event::Key(*event), cx);
} let line = prompt.line();
key!(Esc) => EventResult::Consumed(None), match direction {
_ => { Direction::Forward => {
let event = prompt.handle_event(&Event::Key(*event), cx); self.search_next(line);
let line = prompt.line(); }
if search_next { Direction::Backward => self.search_previous(line),
self.search_next(line) }
} else { self.search_prompt = Some((direction, prompt));
self.search_previous(line) event
} }
self.prompt = Some((action, prompt));
event
} }
} else {
EventResult::Ignored(None)
} }
} }
fn new_search_prompt(&mut self, search_next: bool) { fn new_search_prompt(&mut self, direction: Direction) {
self.save_view(); self.save_view();
self.prompt = Some(( self.search_prompt = Some((
PromptAction::Search { search_next }, direction,
Prompt::new( Prompt::new(
"[SEARCH]: ".into(), "[SEARCH]: ".into(),
None, None,
ui::completers::theme, ui::completers::none,
|_, _, _| {}, |_, _, _| {},
), ),
)) ))
} }
fn new_filter_prompt(&mut self, cx: &mut Context) {
self.save_view();
self.filter_prompt = Some(
Prompt::new(
"[FILTER]: ".into(),
None,
ui::completers::none,
|_, _, _| {},
)
.with_line(self.filter.clone(), cx.editor),
)
}
pub fn prompting(&self) -> bool { pub fn prompting(&self) -> bool {
self.prompt.is_some() self.filter_prompt.is_some() || self.search_prompt.is_some()
} }
} }
@ -1830,6 +1932,86 @@ krabby_patty
.trim() .trim()
); );
} }
#[test]
fn test_refresh() {
let mut view = dummy_tree_view();
// 1. Move to the last child item on the tree
view.move_to_last();
view.move_to_children(&"".to_string()).unwrap();
view.move_to_last();
view.move_to_children(&"".to_string()).unwrap();
view.move_to_last();
view.move_to_children(&"".to_string()).unwrap();
view.move_to_last();
view.move_to_children(&"".to_string()).unwrap();
// 1a. Expect the current selected item is the last child on the tree
assert_eq!(
render(&mut view),
"
epants
[squar]
sq
[uar]
(ar)"
.trim_start_matches(|c| c == '\n')
);
// 2. Refreshes the tree with a filter that will remove the last child
view.refresh(&"ar".to_string()).unwrap();
// 3. Get the current item
let item = view.current_item();
// 3a. Expects no failure
assert_eq!(item.name, "who_lives_in_a_pine")
}
#[test]
fn test_jump_backward() {
let mut view = dummy_tree_view();
view.move_down_half_page();
view.move_down_half_page();
assert_eq!(
render(&mut view),
"
[who_lives_in_a_pineapple_under_the_sea]
gary_the_snail
karen
king_neptune
(krabby_patty)
"
.trim()
);
view.jump_backward();
assert_eq!(
render(&mut view),
"
[who_lives_in_a_pineapple_under_the_sea]
gary_the_snail
(karen)
king_neptune
krabby_patty
"
.trim()
);
view.jump_backward();
assert_eq!(
render(&mut view),
"
(who_lives_in_a_pineapple_under_the_sea)
gary_the_snail
karen
king_neptune
krabby_patty
"
.trim()
);
}
} }
#[cfg(test)] #[cfg(test)]

Loading…
Cancel
Save