diff options
author | Piotr Wach <pwach@bloomberg.net> | 2023-12-11 21:35:12 +0000 |
---|---|---|
committer | Piotr Wach <pwach@bloomberg.net> | 2023-12-19 21:13:17 +0000 |
commit | df6a02cd8fdbe693f507ab34a89227431d7c112e (patch) | |
tree | e3e64cc1653b50940d28405012fdfee984b43538 | |
parent | b23e13431dad1ed9efc6728f4c9ee8ab2254a42c (diff) |
Implements glob search mode
-rw-r--r-- | Cargo.lock | 56 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | src/interactive/app/common.rs | 24 | ||||
-rw-r--r-- | src/interactive/app/eventloop.rs | 309 | ||||
-rw-r--r-- | src/interactive/app/handlers.rs | 245 | ||||
-rw-r--r-- | src/interactive/app/mod.rs | 2 | ||||
-rw-r--r-- | src/interactive/app/navigation.rs | 79 | ||||
-rw-r--r-- | src/interactive/app/tests/journeys_readonly.rs | 36 | ||||
-rw-r--r-- | src/interactive/app/tests/journeys_with_writes.rs | 9 | ||||
-rw-r--r-- | src/interactive/app/tests/unit.rs | 12 | ||||
-rw-r--r-- | src/interactive/app/tests/utils.rs | 9 | ||||
-rw-r--r-- | src/interactive/app/tree_view.rs | 210 | ||||
-rw-r--r-- | src/interactive/mod.rs | 12 | ||||
-rw-r--r-- | src/interactive/widgets/entries.rs | 99 | ||||
-rw-r--r-- | src/interactive/widgets/glob.rs | 173 | ||||
-rw-r--r-- | src/interactive/widgets/help.rs | 1 | ||||
-rw-r--r-- | src/interactive/widgets/main.rs | 83 | ||||
-rw-r--r-- | src/interactive/widgets/mark.rs | 14 | ||||
-rw-r--r-- | src/interactive/widgets/mod.rs | 2 |
19 files changed, 1037 insertions, 339 deletions
@@ -15,6 +15,15 @@ dependencies = [ ] [[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] name = "allocator-api2" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -104,6 +113,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] +name = "bstr" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c" +dependencies = [ + "memchr", + "serde", +] + +[[package]] name = "byte-unit" version = "4.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -301,6 +320,7 @@ dependencies = [ "clap", "crosstermion", "filesize", + "globset", "human_format", "itertools 0.12.0", "jwalk", @@ -360,6 +380,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] +name = "globset" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] name = "hashbrown" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -521,6 +554,12 @@ dependencies = [ ] [[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] name = "memoffset" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -732,6 +771,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" [[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] name = "rustversion" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -40,6 +40,7 @@ wild = "2.0.4" owo-colors = "3.5.0" human_format = "1.0.3" once_cell = "1.19" +globset = "0.4.14" [[bin]] name="dua" diff --git a/src/interactive/app/common.rs b/src/interactive/app/common.rs index 6b455a1..f4d5939 100644 --- a/src/interactive/app/common.rs +++ b/src/interactive/app/common.rs @@ -2,7 +2,8 @@ use crate::interactive::path_of; use dua::traverse::{EntryData, Tree, TreeIndex}; use itertools::Itertools; use petgraph::Direction; -use std::cmp::Ordering; +use std::path::Path; +use std::{cmp::Ordering, path::PathBuf}; use unicode_segmentation::UnicodeSegmentation; #[derive(Default, Debug, Copy, Clone, PartialOrd, PartialEq, Eq)] @@ -50,9 +51,21 @@ pub struct EntryDataBundle { pub data: EntryData, pub is_dir: bool, pub exists: bool, + pub glob_name: Option<PathBuf>, } -pub fn sorted_entries(tree: &Tree, node_idx: TreeIndex, sorting: SortMode) -> Vec<EntryDataBundle> { +impl EntryDataBundle { + pub fn name(&self) -> &Path { + self.glob_name.as_deref().unwrap_or(&self.data.name) + } +} + +pub fn sorted_entries( + tree: &Tree, + node_idx: TreeIndex, + sorting: SortMode, + glob_root: Option<TreeIndex>, +) -> Vec<EntryDataBundle> { use SortMode::*; fn cmp_count(l: &EntryDataBundle, r: &EntryDataBundle) -> Ordering { l.data @@ -63,13 +76,18 @@ pub fn sorted_entries(tree: &Tree, node_idx: TreeIndex, sorting: SortMode) -> Ve tree.neighbors_directed(node_idx, Direction::Outgoing) .filter_map(|idx| { tree.node_weight(idx).map(|w| { - let p = path_of(tree, idx); + let mut use_glob_path = false; + if let Some(glob_root) = glob_root { + use_glob_path = node_idx == glob_root; + } + let p = path_of(tree, idx, glob_root); let pm = p.symlink_metadata(); EntryDataBundle { index: idx, data: w.clone(), exists: pm.is_ok(), is_dir: pm.ok().map_or(false, |m| m.is_dir()), + glob_name: if use_glob_path { Some(p) } else { None }, } }) }) diff --git a/src/interactive/app/eventloop.rs b/src/interactive/app/eventloop.rs index e0773bd..ec31ca1 100644 --- a/src/interactive/app/eventloop.rs +++ b/src/interactive/app/eventloop.rs @@ -1,36 +1,39 @@ use crate::interactive::{ + app::navigation::NavigationState, sorted_entries, - widgets::{MainWindow, MainWindowProps}, + widgets::{glob_search, MainWindow, MainWindowProps}, ByteVisualization, CursorDirection, CursorMode, DisplayOptions, EntryDataBundle, MarkEntryMode, SortMode, }; use anyhow::Result; use crosstermion::input::{input_channel, Event, Key}; use dua::{ - traverse::{Traversal, TreeIndex}, + traverse::{EntryData, Traversal}, WalkOptions, WalkResult, }; -use std::{collections::BTreeMap, path::PathBuf}; +use std::path::PathBuf; use tui::backend::Backend; use tui_react::Terminal; -#[derive(Default, Copy, Clone)] +use super::tree_view::{GlobTreeView, NormalTreeView, TreeView}; + +#[derive(Default, Copy, Clone, PartialEq)] pub enum FocussedPane { #[default] Main, Help, Mark, + Glob, } #[derive(Default)] pub struct AppState { - pub root: TreeIndex, - pub selected: Option<TreeIndex>, + pub normal_mode: NavigationState, + pub glob_mode: Option<NavigationState>, pub entries: Vec<EntryDataBundle>, pub sorting: SortMode, pub message: Option<String>, pub focussed: FocussedPane, - pub bookmarks: BTreeMap<TreeIndex, TreeIndex>, pub is_scanning: bool, } @@ -40,10 +43,18 @@ pub enum ProcessingResult { } impl AppState { + pub fn navigation_mut(&mut self) -> &mut NavigationState { + self.glob_mode.as_mut().unwrap_or(&mut self.normal_mode) + } + + pub fn navigation(&self) -> &NavigationState { + self.glob_mode.as_ref().unwrap_or(&self.normal_mode) + } + pub fn draw<B>( &mut self, window: &mut MainWindow, - traversal: &Traversal, + tree_view: &dyn TreeView, display: DisplayOptions, terminal: &mut Terminal<B>, ) -> Result<()> @@ -51,7 +62,11 @@ impl AppState { B: Backend, { let props = MainWindowProps { - traversal, + current_path: tree_view.current_path(self.navigation().view_root), + entries_traversed: tree_view.traversal().entries_traversed, + total_bytes: tree_view.traversal().total_bytes, + start: tree_view.traversal().start, + elapsed: tree_view.traversal().elapsed, display, state: self, }; @@ -72,94 +87,221 @@ impl AppState { use crosstermion::input::Key::*; use FocussedPane::*; - self.draw(window, traversal, *display, terminal)?; + { + let tree_view = self.tree_view(traversal); + self.draw(window, tree_view.as_ref(), *display, terminal)?; + } + for event in events { let key = match event { Event::Key(key) => key, Event::Resize(_, _) => Alt('\r'), }; + let mut tree_view = self.tree_view(traversal); + self.reset_message(); + let mut handled = true; match key { - Char('?') => self.toggle_help_pane(window), + Char('?') if self.focussed != FocussedPane::Glob => self.toggle_help_pane(window), + Char('/') if self.focussed != FocussedPane::Glob => { + self.toggle_glob_search(window); + } Char('\t') => { self.cycle_focus(window); } - Ctrl('c') => { + Ctrl('c') if self.focussed != FocussedPane::Glob => { return Ok(ProcessingResult::ExitRequested(WalkResult { - num_errors: traversal.io_errors, + num_errors: tree_view.traversal().io_errors, })) } - Char('q') | Esc => match self.focussed { - Main => { - return Ok(ProcessingResult::ExitRequested(WalkResult { - num_errors: traversal.io_errors, - })) + Char('q') if self.focussed != FocussedPane::Glob => { + if let Some(value) = self.handle_quit(tree_view.as_mut(), window) { + return value; } - Mark => self.focussed = Main, - Help => { - self.focussed = Main; - window.help_pane = None + } + Esc => { + if let Some(value) = self.handle_quit(tree_view.as_mut(), window) { + return value; } - }, - _ => {} + } + _ => { + handled = false; + } } - match self.focussed { - Mark => self.dispatch_to_mark_pane(key, window, traversal, *display, terminal), - Help => { - window - .help_pane - .as_mut() - .expect("help pane") - .process_events(key); - } - Main => match key { - Char('O') => self.open_that(traversal), - Char(' ') => self.mark_entry( - CursorMode::KeepPosition, - MarkEntryMode::Toggle, - window, - traversal, - ), - Char('d') => self.mark_entry( - CursorMode::Advance, - MarkEntryMode::Toggle, + if !handled { + match self.focussed { + Mark => self.dispatch_to_mark_pane( + key, window, - traversal, + tree_view.as_mut(), + *display, + terminal, ), - Char('x') => self.mark_entry( - CursorMode::Advance, - MarkEntryMode::MarkForDeletion, - window, - traversal, - ), - Char('a') => self.mark_all_entries(MarkEntryMode::Toggle, window, traversal), - Char('u') | Char('h') | Backspace | Left => { - self.exit_node_with_traversal(traversal) + Help => { + window + .help_pane + .as_mut() + .expect("help pane") + .process_events(key); } - Char('o') | Char('l') | Char('\n') | Right => { - self.enter_node_with_traversal(traversal) + Glob => { + let glob_pane = window.glob_pane.as_mut().expect("glob pane"); + match key { + Char('\n') => { + self.search_glob_pattern(tree_view.as_mut(), &glob_pane.input) + } + _ => glob_pane.process_events(key), + } } - Char('H') | Home => self.change_entry_selection(CursorDirection::ToTop), - Char('G') | End => self.change_entry_selection(CursorDirection::ToBottom), - Ctrl('u') | PageUp => self.change_entry_selection(CursorDirection::PageUp), - Char('k') | Up => self.change_entry_selection(CursorDirection::Up), - Char('j') | Down => self.change_entry_selection(CursorDirection::Down), - Ctrl('d') | PageDown => self.change_entry_selection(CursorDirection::PageDown), - Char('s') => self.cycle_sorting(traversal), - Char('m') => self.cycle_mtime_sorting(traversal), - Char('c') => self.cycle_count_sorting(traversal), - Char('g') => display.byte_vis.cycle(), - _ => {} - }, - }; - self.draw(window, traversal, *display, terminal)?; + Main => match key { + Char('O') => self.open_that(tree_view.as_ref()), + Char(' ') => self.mark_entry( + CursorMode::KeepPosition, + MarkEntryMode::Toggle, + window, + tree_view.as_ref(), + ), + Char('d') => self.mark_entry( + CursorMode::Advance, + MarkEntryMode::Toggle, + window, + tree_view.as_ref(), + ), + Char('x') => self.mark_entry( + CursorMode::Advance, + MarkEntryMode::MarkForDeletion, + window, + tree_view.as_ref(), + ), + Char('a') => { + self.mark_all_entries(MarkEntryMode::Toggle, window, tree_view.as_ref()) + } + Char('u') | Char('h') | Backspace | Left => { + self.exit_node_with_traversal(tree_view.as_ref()) + } + Char('o') | Char('l') | Char('\n') | Right => { + self.enter_node_with_traversal(tree_view.as_ref()) + } + Char('H') | Home => self.change_entry_selection(CursorDirection::ToTop), + Char('G') | End => self.change_entry_selection(CursorDirection::ToBottom), + Ctrl('u') | PageUp => self.change_entry_selection(CursorDirection::PageUp), + Char('k') | Up => self.change_entry_selection(CursorDirection::Up), + Char('j') | Down => self.change_entry_selection(CursorDirection::Down), + Ctrl('d') | PageDown => { + self.change_entry_selection(CursorDirection::PageDown) + } + Char('s') => self.cycle_sorting(tree_view.as_ref()), + Char('m') => self.cycle_mtime_sorting(tree_view.as_ref()), + Char('c') => self.cycle_count_sorting(tree_view.as_ref()), + Char('g') => display.byte_vis.cycle(), + _ => {} + }, + }; + } + self.draw(window, tree_view.as_ref(), *display, terminal)?; } Ok(ProcessingResult::Finished(WalkResult { num_errors: traversal.io_errors, })) } + + fn tree_view<'a>(&mut self, traversal: &'a mut Traversal) -> Box<dyn TreeView + 'a> { + let tree_view: Box<dyn TreeView> = if let Some(glob_source) = &self.glob_mode { + Box::new(GlobTreeView { + traversal, + glob_tree_root: glob_source.tree_root, + }) + } else { + Box::new(NormalTreeView { traversal }) + }; + tree_view + } + + fn search_glob_pattern(&mut self, tree_view: &mut dyn TreeView, glob_pattern: &str) { + use FocussedPane::*; + let search_results = + glob_search(tree_view.tree(), self.normal_mode.view_root, glob_pattern); + match search_results { + Ok(search_results) => { + if let Some(glob_source) = &self.glob_mode { + tree_view.tree_as_mut().remove_node(glob_source.tree_root); + } + + let tree_root = tree_view.tree_as_mut().add_node(EntryData::default()); + let glob_source = NavigationState { + tree_root, + view_root: tree_root, + selected: Some(tree_root), + ..Default::default() + }; + self.glob_mode = Some(glob_source); + + for idx in search_results { + tree_view.tree_as_mut().add_edge(tree_root, idx, ()); + } + + let new_entries = + sorted_entries(tree_view.tree(), tree_root, self.sorting, Some(tree_root)); + + let new_entries = self + .navigation_mut() + .selected + .map(|previously_selected| (previously_selected, new_entries)); + + self.enter_node(new_entries); + self.focussed = Main; + } + _ => self.message = Some("Glob search error!".into()), + } + } + + fn handle_quit( + &mut self, + tree_view: &mut dyn TreeView, + window: &mut MainWindow, + ) -> Option<std::result::Result<ProcessingResult, anyhow::Error>> { + use FocussedPane::*; + match self.focussed { + Main => { + if self.glob_mode.is_some() { + self.handle_glob_quit(tree_view, window); + } else { + return Some(Ok(ProcessingResult::ExitRequested(WalkResult { + num_errors: tree_view.traversal().io_errors, + }))); + } + } + Mark => self.focussed = Main, + Help => { + self.focussed = Main; + window.help_pane = None + } + Glob => { + self.handle_glob_quit(tree_view, window); + } + } + None + } + + fn handle_glob_quit(&mut self, tree_view: &mut dyn TreeView, window: &mut MainWindow) { + use FocussedPane::*; + self.focussed = Main; + if let Some(glob_source) = &self.glob_mode { + tree_view.tree_as_mut().remove_node(glob_source.tree_root); + } + self.glob_mode = None; + window.glob_pane = None; + + let new_entries = self.navigation().selected.map(|previously_selected| { + ( + previously_selected, + tree_view.sorted_entries(self.navigation().view_root, self.sorting), + ) + }); + self.enter_node(new_entries); + } } pub fn draw_window<B>( @@ -171,7 +313,7 @@ where B: Backend, { let area = terminal.pre_render()?; - window.render(props, area, terminal.current_buffer_mut()); + window.render(props, area, terminal); terminal.post_render()?; Ok(()) } @@ -202,6 +344,7 @@ impl TerminalApp { ) .ok(); } + pub fn process_events<B>( &mut self, terminal: &mut Terminal<B>, @@ -258,11 +401,16 @@ impl TerminalApp { let mut received_events = false; let traversal = Traversal::from_walk(options, input_paths, |traversal| { if !received_events { - state.root = traversal.root_index; + state.navigation_mut().view_root = traversal.root_index; } - state.entries = sorted_entries(&traversal.tree, state.root, state.sorting); + state.entries = sorted_entries( + &traversal.tree, + state.navigation().view_root, + state.sorting, + state.glob_root(), + ); if !received_events { - state.selected = state.entries.first().map(|b| b.index); + state.navigation_mut().selected = state.entries.first().map(|b| b.index); } state.reset_message(); // force "scanning" to appear @@ -279,6 +427,7 @@ impl TerminalApp { ProcessingResult::ExitRequested(_) => true, ProcessingResult::Finished(_) => false, }; + Ok(should_exit) })?; @@ -289,10 +438,16 @@ impl TerminalApp { state.is_scanning = false; if !received_events { - state.root = traversal.root_index; + state.navigation_mut().view_root = traversal.root_index; } - state.entries = sorted_entries(&traversal.tree, state.root, state.sorting); - state.selected = state + state.entries = sorted_entries( + &traversal.tree, + state.navigation().view_root, + state.sorting, + state.glob_root(), + ); + state.navigation_mut().selected = state + .navigation() .selected .filter(|_| received_events) .or_else(|| state.entries.first().map(|b| b.index)); diff --git a/src/interactive/app/handlers.rs b/src/interactive/app/handlers.rs index d3761e9..3367bf3 100644 --- a/src/interactive/app/handlers.rs +++ b/src/interactive/app/handlers.rs @@ -1,13 +1,11 @@ use crate::interactive::{ + app::tree_view::TreeView, app::FocussedPane::*, - path_of, sorted_entries, - widgets::{HelpPane, MainWindow, MarkMode, MarkPane}, + widgets::{GlobPane, HelpPane, MainWindow, MarkMode, MarkPane}, AppState, DisplayOptions, EntryDataBundle, }; use crosstermion::input::Key; -use dua::traverse::{Traversal, TreeIndex}; -use itertools::Itertools; -use petgraph::{visit::Bfs, Direction}; +use dua::traverse::TreeIndex; use std::{fs, io, path::PathBuf}; use tui::backend::Backend; use tui_react::Terminal; @@ -48,29 +46,27 @@ impl CursorDirection { } impl AppState { - pub fn open_that(&self, traversal: &Traversal) { - if let Some(idx) = self.selected { - open::that(path_of(&traversal.tree, idx)).ok(); + pub fn open_that(&self, tree_view: &dyn TreeView) { + if let Some(idx) = self.navigation().selected { + open::that(tree_view.path_of(idx)).ok(); } } - pub fn exit_node_with_traversal(&mut self, traversal: &Traversal) { - let entries = self.entries_for_exit_node(traversal); + pub fn exit_node_with_traversal(&mut self, tree_view: &dyn TreeView) { + let entries = self.entries_for_exit_node(tree_view); self.exit_node(entries); } fn entries_for_exit_node( &self, - traversal: &Traversal, + tree_view: &dyn TreeView, ) -> Option<(TreeIndex, Vec<EntryDataBundle>)> { - traversal - .tree - .neighbors_directed(self.root, Direction::Incoming) - .next() + tree_view + .view_parent_of(self.navigation().view_root) .map(|parent_idx| { ( parent_idx, - sorted_entries(&traversal.tree, parent_idx, self.sorting), + tree_view.sorted_entries(parent_idx, self.sorting), ) }) } @@ -78,13 +74,8 @@ impl AppState { pub fn exit_node(&mut self, entries: Option<(TreeIndex, Vec<EntryDataBundle>)>) { match entries { Some((parent_idx, entries)) => { - self.root = parent_idx; + self.navigation_mut().exit_node(parent_idx, &entries); self.entries = entries; - self.selected = self - .bookmarks - .get(&parent_idx) - .copied() - .or_else(|| self.entries.first().map(|b| b.index)); } None => self.message = Some("Top level reached".into()), } @@ -92,38 +83,30 @@ impl AppState { fn entries_for_enter_node( &self, - traversal: &Traversal, + tree_view: &dyn TreeView, ) -> Option<(TreeIndex, Vec<EntryDataBundle>)> { - self.selected.map(|previously_selected| { + self.navigation().selected.map(|previously_selected| { ( previously_selected, - sorted_entries(&traversal.tree, previously_selected, self.sorting), + tree_view.sorted_entries(previously_selected, self.sorting), ) }) } - pub fn enter_node_with_traversal(&mut self, traversal: &Traversal) { - let new_entries = self.entries_for_enter_node(traversal); + pub fn enter_node_with_traversal(&mut self, tree_view: &dyn TreeView) { + let new_entries = self.entries_for_enter_node(tree_view); self.enter_node(new_entries) } pub fn enter_node(&mut self, entries_at_selected: Option<(TreeIndex, Vec<EntryDataBundle>)>) { if let Some((previously_selected, new_entries)) = entries_at_selected { - match new_entries.get( - self.bookmarks - .get(&previously_selected) - .and_then(|selected| { - new_entries - .iter() - .find_position(|b| b.index == *selected) - .map(|(pos, _)| pos) - }) - .unwrap_or(0), - ) { - Some(b) => { - self.bookmarks.insert(self.root, previously_selected); - self.root = previously_selected; - self.selected = Some(b.index); + match self + .navigation() + .get_previously_selected_index(previously_selected, &new_entries) + { + Some(selected) => { + self.navigation_mut() + .enter_node(previously_selected, selected); self.entries = new_entries; } None => self.message = Some("Entry is a file or an empty directory".into()), @@ -132,38 +115,36 @@ impl AppState { } pub fn change_entry_selection(&mut self, direction: CursorDirection) { - let entries = &self.entries; - let next_selected_pos = match self.selected { - Some(ref selected) => entries - .iter() - .find_position(|b| b.index == *selected) - .map(|(idx, _)| direction.move_cursor(idx)) - .unwrap_or(0), - None => 0, - }; - self.selected = entries - .get(next_selected_pos) - .or_else(|| entries.last()) - .map(|b| b.index) - .or(self.selected); - if let Some(selected) = self.selected { - self.bookmarks.insert(self.root, selected); - } + let nex_index = self.navigation().get_next_index(direction, &self.entries); + self.navigation_mut().select(nex_index); } - pub fn cycle_sorting(&mut self, traversal: &Traversal) { + pub fn cycle_sorting(&mut self, tree_view: &dyn TreeView) { self.sorting.toggle_size(); - self.entries = sorted_entries(&traversal.tree, self.root, self.sorting); + self.entries = tree_view.sorted_entries(self.navigation().view_root, self.sorting); } - pub fn cycle_mtime_sorting(&mut self, traversal: &Traversal) { + pub fn cycle_mtime_sorting(&mut self, tree_view: &dyn TreeView) { self.sorting.toggle_mtime(); - self.entries = sorted_entries(&traversal.tree, self.root, self.sorting); + self.entries = tree_view.sorted_entries(self.navigation().view_root, self.sorting); } - pub fn cycle_count_sorting(&mut self, traversal: &Traversal) { + pub fn cycle_count_sorting(&mut self, tree_view: &dyn TreeView) { self.sorting.toggle_count(); - self.entries = sorted_entries(&traversal.tree, self.root, self.sorting); + self.entries = tree_view.sorted_entries(self.navigation().view_root, self.sorting); + } + + pub fn toggle_glob_search(&mut self, window: &mut MainWindow) { + self.focussed = match self.focussed { + Main | Mark | Help => { + window.glob_pane = Some(GlobPane::default()); + Glob + } + Glob => { + window.glob_pane = None; + Main + } + } } pub fn reset_message(&mut self) { @@ -176,7 +157,7 @@ impl AppState { pub fn toggle_help_pane(&mut self, window: &mut MainWindow) { self.focussed = match self.focussed { - Main | Mark => { + Main | Mark | Glob => { window.help_pane = Some(HelpPane::default()); Help } @@ -190,19 +171,28 @@ impl AppState { if let Some(p) = window.mark_pane.as_mut() { p.set_focus(false) }; - self.focussed = match (self.focussed, &window.help_pane, &mut window.mark_pane) { - (Main, Some(_), _) => Help, - (Help, _, Some(ref mut pane)) => { + self.focussed = match ( + self.focussed, + &window.help_pane, + &mut window.mark_pane, + &mut window.glob_pane, + ) { + (Main, Some(_), _, _) => Help, + (Help, _, Some(ref mut pane), _) => { pane.set_focus(true); Mark } - (Help, _, None) => Main, - (Mark, _, _) => Main, - (Main, None, None) => Main, - (Main, None, Some(ref mut pane)) => { + (Help, _, _, Some(_)) => Glob, + (Help, _, None, None) => Main, + (Mark, _, _, Some(_)) => Glob, + (Mark, _, _, _) => Main, + (Main, None, None, None) => Main, + (Main, None, Some(ref mut pane), _) => { pane.set_focus(true); Mark } + (Main, None, None, Some(_)) => Glob, + (Glob, _, _, _) => Main, }; } @@ -210,7 +200,7 @@ impl AppState { &mut self, key: Key,< |