diff options
author | Sebastian Thiel <sebastian.thiel@icloud.com> | 2023-12-24 10:46:06 +0100 |
---|---|---|
committer | Sebastian Thiel <sebastian.thiel@icloud.com> | 2023-12-24 10:46:06 +0100 |
commit | 1c4d6a77c9f439782446d5d5f791fe9e809de0e7 (patch) | |
tree | 2ab45e65481b661b2059337fb4d6204a019a801e | |
parent | b23e13431dad1ed9efc6728f4c9ee8ab2254a42c (diff) | |
parent | 2e1858ca519fd2a6fbf4839a23abcf17588dcc32 (diff) |
Merge branch 'glob-review'
-rw-r--r-- | Cargo.lock | 117 | ||||
-rw-r--r-- | Cargo.toml | 8 | ||||
-rw-r--r-- | src/interactive/app/common.rs | 61 | ||||
-rw-r--r-- | src/interactive/app/eventloop.rs | 330 | ||||
-rw-r--r-- | src/interactive/app/handlers.rs | 241 | ||||
-rw-r--r-- | src/interactive/app/mod.rs | 2 | ||||
-rw-r--r-- | src/interactive/app/navigation.rs | 77 | ||||
-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 | 19 | ||||
-rw-r--r-- | src/interactive/app/tree_view.rs | 115 | ||||
-rw-r--r-- | src/interactive/mod.rs | 12 | ||||
-rw-r--r-- | src/interactive/widgets/entries.rs | 262 | ||||
-rw-r--r-- | src/interactive/widgets/glob.rs | 216 | ||||
-rw-r--r-- | src/interactive/widgets/help.rs | 5 | ||||
-rw-r--r-- | src/interactive/widgets/main.rs | 86 | ||||
-rw-r--r-- | src/interactive/widgets/mark.rs | 14 | ||||
-rw-r--r-- | src/interactive/widgets/mod.rs | 2 | ||||
-rw-r--r-- | src/traverse.rs | 3 |
20 files changed, 1226 insertions, 401 deletions
@@ -104,6 +104,17 @@ 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", + "regex-automata", + "serde", +] + +[[package]] name = "byte-unit" version = "4.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -296,11 +307,14 @@ version = "2.23.0" dependencies = [ "anyhow", "atty", + "bstr", "byte-unit", "chrono", "clap", "crosstermion", "filesize", + "gix-glob", + "gix-path", "human_format", "itertools 0.12.0", "jwalk", @@ -314,6 +328,7 @@ dependencies = [ "trash", "tui-react", "unicode-segmentation", + "unicode-width", "wild", ] @@ -330,6 +345,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] +name = "faster-hex" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183" +dependencies = [ + "serde", +] + +[[package]] name = "filesize" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -354,6 +378,58 @@ dependencies = [ ] [[package]] +name = "gix-features" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d46a4a5c6bb5bebec9c0d18b65ada20e6517dbd7cf855b87dd4bbdce3a771b2" +dependencies = [ + "gix-hash", + "gix-trace", + "libc", +] + +[[package]] +name = "gix-glob" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5db19298c5eeea2961e5b3bf190767a2d1f09b8802aeb5f258e42276350aff19" +dependencies = [ + "bitflags 2.4.1", + "bstr", + "gix-features", + "gix-path", +] + +[[package]] +name = "gix-hash" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f8cf8c2266f63e582b7eb206799b63aa5fa68ee510ad349f637dfe2d0653de0" +dependencies = [ + "faster-hex", + "thiserror", +] + +[[package]] +name = "gix-path" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86d6fac2fabe07b67b7835f46d07571f68b11aa1aaecae94fe722ea4ef305e1" +dependencies = [ + "bstr", + "gix-trace", + "home", + "once_cell", + "thiserror", +] + +[[package]] +name = "gix-trace" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b686a35799b53a9825575ca3f06481d0a053a409c4d97ffcf5ddd67a8760b497" + +[[package]] name = "glob" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -391,6 +467,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] name = "human_format" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -521,6 +606,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 +823,12 @@ 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" + +[[package]] name = "rustversion" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -851,6 +948,26 @@ dependencies = [ ] [[package]] +name = "thiserror" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "tinyvec" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -14,7 +14,7 @@ default = ["tui-crossplatform", "trash-move"] tui-unix = ["crosstermion/tui-react-termion", "tui-shared"] tui-crossplatform = ["crosstermion/tui-react-crossterm", "tui-shared"] -tui-shared = ["tui", "tui-react", "open", "unicode-segmentation"] +tui-shared = ["tui", "tui-react", "open", "unicode-segmentation", "unicode-width"] trash-move = ["trash"] [dependencies] @@ -32,6 +32,7 @@ chrono = { version = "0.4.31", default-features = false, features = ["std"] } # 'tui' related unicode-segmentation = { version = "1.3.0", optional = true } +unicode-width = { version = "0.1.5", optional = true } crosstermion = { version = "0.12.0", default-features = false, optional = true } tui = { package = "ratatui", version = "0.24.0", optional = true, default-features = false } tui-react = { version = "0.21.0", optional = true } @@ -40,6 +41,9 @@ wild = "2.0.4" owo-colors = "3.5.0" human_format = "1.0.3" once_cell = "1.19" +gix-glob = "0.14.1" +gix-path = "0.10.1" +bstr = "1.8.0" [[bin]] name="dua" @@ -53,7 +57,7 @@ panic = 'abort' incremental = false overflow-checks = false lto = "fat" -codegen-units = 1 +#codegen-units = 1 build-override = { opt-level = 3 } [dev-dependencies] diff --git a/src/interactive/app/common.rs b/src/interactive/app/common.rs index 6b455a1..3b79a9a 100644 --- a/src/interactive/app/common.rs +++ b/src/interactive/app/common.rs @@ -1,8 +1,9 @@ use crate::interactive::path_of; -use dua::traverse::{EntryData, Tree, TreeIndex}; +use dua::traverse::{Tree, TreeIndex}; use itertools::Itertools; use petgraph::Direction; -use std::cmp::Ordering; +use std::time::SystemTime; +use std::{cmp::Ordering, path::PathBuf}; use unicode_segmentation::UnicodeSegmentation; #[derive(Default, Debug, Copy, Clone, PartialOrd, PartialEq, Eq)] @@ -47,37 +48,61 @@ impl SortMode { pub struct EntryDataBundle { pub index: TreeIndex, - pub data: EntryData, + pub name: PathBuf, + pub size: u128, + pub mtime: SystemTime, + pub entry_count: Option<u64>, pub is_dir: bool, pub exists: bool, } -pub fn sorted_entries(tree: &Tree, node_idx: TreeIndex, sorting: SortMode) -> Vec<EntryDataBundle> { +/// Note that with `glob_root` present, we will not obtain metadata anymore as we might be seeing +/// a lot of entries. That way, displaying 250k entries is no problem. +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 - .entry_count - .cmp(&r.data.entry_count) - .then_with(|| l.data.name.cmp(&r.data.name)) + l.entry_count + .cmp(&r.entry_count) + .then_with(|| l.name.cmp(&r.name)) } tree.neighbors_directed(node_idx, Direction::Outgoing) .filter_map(|idx| { - tree.node_weight(idx).map(|w| { - let p = path_of(tree, idx); - let pm = p.symlink_metadata(); + tree.node_weight(idx).map(|entry| { + let use_glob_path = glob_root.map_or(false, |glob_root| glob_root == node_idx); + let (path, exists, is_dir) = { + let path = path_of(tree, idx, glob_root); + if glob_root.is_some() { + (path, true, entry.is_dir) + } else { + let meta = path.symlink_metadata(); + (path, meta.is_ok(), meta.ok().map_or(false, |m| m.is_dir())) + } + }; EntryDataBundle { index: idx, - data: w.clone(), - exists: pm.is_ok(), - is_dir: pm.ok().map_or(false, |m| m.is_dir()), + name: if use_glob_path { + path + } else { + entry.name.clone() + }, + size: entry.size, + mtime: entry.mtime, + entry_count: entry.entry_count, + exists, + is_dir, } }) }) .sorted_by(|l, r| match sorting { - SizeDescending => r.data.size.cmp(&l.data.size), - SizeAscending => l.data.size.cmp(&r.data.size), - MTimeAscending => l.data.mtime.cmp(&r.data.mtime), - MTimeDescending => r.data.mtime.cmp(&l.data.mtime), + SizeDescending => r.size.cmp(&l.size), + SizeAscending => l.size.cmp(&r.size), + MTimeAscending => l.mtime.cmp(&r.mtime), + MTimeDescending => r.mtime.cmp(&l.mtime), CountAscending => cmp_count(l, r), CountDescending => cmp_count(l, r).reverse(), }) diff --git a/src/interactive/app/eventloop.rs b/src/interactive/app/eventloop.rs index e0773bd..00b37e5 100644 --- a/src/interactive/app/eventloop.rs +++ b/src/interactive/app/eventloop.rs @@ -1,36 +1,46 @@ use crate::interactive::{ + app::navigation::Navigation, 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::TreeView; + +#[derive(Default, Copy, Clone, PartialEq)] pub enum FocussedPane { #[default] Main, Help, Mark, + Glob, +} + +#[derive(Default)] +pub struct Cursor { + pub show: bool, + pub x: u16, + pub y: u16, } #[derive(Default)] pub struct AppState { - pub root: TreeIndex, - pub selected: Option<TreeIndex>, + pub navigation: Navigation, + pub glob_navigation: Option<Navigation>, 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 +50,20 @@ pub enum ProcessingResult { } impl AppState { + pub fn navigation_mut(&mut self) -> &mut Navigation { + self.glob_navigation + .as_mut() + .unwrap_or(&mut self.navigation) + } + + pub fn navigation(&self) -> &Navigation { + self.glob_navigation.as_ref().unwrap_or(&self.navigation) + } + pub fn draw<B>( &mut self, window: &mut MainWindow, - traversal: &Traversal, + tree_view: &TreeView<'_>, display: DisplayOptions, terminal: &mut Terminal<B>, ) -> Result<()> @@ -51,11 +71,26 @@ 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, }; - draw_window(window, props, terminal) + + let mut cursor = Cursor::default(); + let result = draw_window(window, props, terminal, &mut cursor); + + if cursor.show { + _ = terminal.show_cursor(); + _ = terminal.set_cursor(cursor.x, cursor.y); + } else { + _ = terminal.hide_cursor(); + } + + result } pub fn process_events<B>( @@ -72,7 +107,11 @@ 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, *display, terminal)?; + } + for event in events { let key = match event { Event::Key(key) => key, @@ -80,99 +119,215 @@ impl AppState { }; self.reset_message(); + + let glob_focussed = self.focussed == Glob; + let mut tree_view = self.tree_view(traversal); + let mut handled = true; match key { - Char('?') => self.toggle_help_pane(window), + Esc => { + if let Some(value) = self.handle_quit(&mut tree_view, window) { + return value; + } + } Char('\t') => { self.cycle_focus(window); } - Ctrl('c') => { + Char('/') if !glob_focussed => { + self.toggle_glob_search(window); + } + Char('?') if !glob_focussed => self.toggle_help_pane(window), + Ctrl('c') if !glob_focussed => { 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, - })) - } - Mark => self.focussed = Main, - Help => { - self.focussed = Main; - window.help_pane = None + Char('q') if !glob_focussed => { + if let Some(value) = self.handle_quit(&mut tree_view, 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, - window, - traversal, - ), - 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) + if !handled { + match self.focussed { + Mark => { + self.dispatch_to_mark_pane(key, window, &mut tree_view, *display, terminal) } - Char('o') | Char('l') | Char('\n') | Right => { - self.enter_node_with_traversal(traversal) + Help => { + window + .help_pane + .as_mut() + .expect("help 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)?; + Glob => { + let glob_pane = window.glob_pane.as_mut().expect("glob pane"); + match key { + Char('\n') => { + self.search_glob_pattern(&mut tree_view, &glob_pane.input) + } + _ => glob_pane.process_events(key), + } + } + Main => match key { + Char('O') => self.open_that(&tree_view), + Char(' ') => self.mark_entry( + CursorMode::KeepPosition, + MarkEntryMode::Toggle, + window, + &tree_view, + ), + Char('d') => self.mark_entry( + CursorMode::Advance, + MarkEntryMode::Toggle, + window, + &tree_view, + ), + Char('x') => self.mark_entry( + CursorMode::Advance, + MarkEntryMode::MarkForDeletion, + window, + &tree_view, + ), + Char('a') => { + self.mark_all_entries(MarkEntryMode::Toggle, window, &tree_view) + } + Char('u') | Char('h') | Backspace | Left => { + self.exit_node_with_traversal(&tree_view) + } + Char('o') | Char('l') | Char('\n') | Right => { + self.enter_node_with_traversal(&tree_view) + } + 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), + Char('m') => self.cycle_mtime_sorting(&tree_view), + Char('c') => self.cycle_count_sorting(&tree_view), + Char('g') => display.byte_vis.cycle(), + _ => {} + }, + }; + } + self.draw(window, &tree_view, *display, terminal)?; } Ok(ProcessingResult::Finished(WalkResult { num_errors: traversal.io_errors, })) } + + fn tree_view<'a>(&mut self, traversal: &'a mut Traversal) -> TreeView<'a> { + TreeView { + traversal, + glob_tree_root: self.glob_navigation.as_ref().map(|n| n.tree_root), + } + } + + fn search_glob_pattern(&mut self, tree_view: &mut TreeView, glob_pattern: &str) { + use FocussedPane::*; + match glob_search(tree_view.tree(), self.navigation.view_root, glob_pattern) { + Ok(matches) if matches.is_empty() => { + self.message = Some("No match found".into()); + } + Ok(matches) => { + if let Some(glob_source) = &self.glob_navigation { + tree_view.tree_mut().remove_node(glob_source.tree_root); + } + + let tree_root = tree_view.tree_mut().add_node(EntryData::default()); + let glob_source = Navigation { + tree_root, + view_root: tree_root, + selected: Some(tree_root), + ..Default::default() + }; + self.glob_navigation = Some(glob_source); + + for idx in matches { + tree_view.tree_mut().add_edge(tree_root, idx, ()); + } + + let glob_tree_view = TreeView { + traversal: tree_view.traversal, + glob_tree_root: Some(tree_root), + }; + let new_entries = glob_tree_view.sorted_entries(tree_root, self.sorting); + + let new_entries = self + .navigation_mut() + .selected + .map(|previously_selected| (previously_selected, new_entries)); + + self.enter_node(new_entries); + self.focussed = Main; + } + Err(err) => self.message = Some(err.to_string()), + } + } + + fn handle_quit( + &mut self, + tree_view: &mut TreeView<'_>, + window: &mut MainWindow, + ) -> Option<std::result::Result<ProcessingResult, anyhow::Error>> { + use FocussedPane::*; + match self.focussed { + Main => { + if self.glob_navigation.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 TreeView<'_>, window: &mut MainWindow) { + use FocussedPane::*; + self.focussed = Main; + if let Some(glob_source) = &self.glob_navigation { + tree_view.tree_mut().remove_node(glob_source.tree_root); + } + self.glob_navigation = None; + window.glob_pane = None; + + tree_view.glob_tree_root.take(); + self.entries = tree_view.sorted_entries(self.navigation().view_root, self.sorting); + } } pub fn draw_window<B>( window: &mut MainWindow, props: MainWindowProps, terminal: &mut Terminal<B>, + cursor: &mut Cursor, ) -> Result<()> where B: Backend, { let area = terminal.pre_render()?; - window.render(props, area, terminal.current_buffer_mut()); + window.render(props, area, terminal, cursor); terminal.post_render()?; + Ok(()) } @@ -202,6 +357,7 @@ impl TerminalApp { ) .ok(); } + pub fn process_events<B>( &mut self, terminal: &mut Terminal<B>, @@ -258,11 +414,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 +440,7 @@ impl TerminalApp { ProcessingResult::ExitRequested(_) => true, ProcessingResult::Finished(_) => false, }; + Ok(should_exit) })?; @@ -289,10 +451,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..6e12c3d 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: &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: &TreeView<'_>) { + let entries = self.entries_for_exit_node(tree_view); self.exit_node(entries); } fn entries_for_exit_node( &self, - traversal: &Traversal, + tree_view: &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 { |