From 78b9a8e22568c902132ed98d32e223ff71eb7b06 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 23 Jan 2024 15:24:52 +0100 Subject: feat: add `dua i --no-entry-check` flag. (#227) With it, in interactive mode, entries will not be checked for presence. This can avoid laggy behaviour when switching between directories as `lstat` calls will not run, which can be slow on some filesystems. --- src/interactive/app/common.rs | 19 +++++++++++++++++-- src/interactive/app/eventloop.rs | 14 +++++++++----- src/interactive/app/handlers.rs | 14 +++++++------- src/interactive/app/state.rs | 3 +++ src/interactive/app/terminal.rs | 5 ++++- src/interactive/app/tests/utils.rs | 9 +++++++-- src/interactive/app/tree_view.rs | 6 +++--- src/main.rs | 6 +++++- src/options.rs | 3 +++ 9 files changed, 58 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/interactive/app/common.rs b/src/interactive/app/common.rs index 7c89a2f..433a793 100644 --- a/src/interactive/app/common.rs +++ b/src/interactive/app/common.rs @@ -56,6 +56,21 @@ pub struct EntryDataBundle { pub exists: bool, } +pub enum EntryCheck { + PossiblyCostlyLstat, + Disabled, +} + +impl EntryCheck { + pub fn new(is_scanning: bool, allow_entry_check: bool) -> Self { + if allow_entry_check && !is_scanning { + EntryCheck::PossiblyCostlyLstat + } else { + EntryCheck::Disabled + } + } +} + /// 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( @@ -63,7 +78,7 @@ pub fn sorted_entries( node_idx: TreeIndex, sorting: SortMode, glob_root: Option, - is_scanning: bool, + check: EntryCheck, ) -> Vec { use SortMode::*; fn cmp_count(l: &EntryDataBundle, r: &EntryDataBundle) -> Ordering { @@ -77,7 +92,7 @@ pub fn sorted_entries( 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 is_scanning || glob_root == Some(node_idx) { + if matches!(check, EntryCheck::Disabled) || glob_root == Some(node_idx) { (path, true, entry.is_dir) } else { let meta = path.symlink_metadata(); diff --git a/src/interactive/app/eventloop.rs b/src/interactive/app/eventloop.rs index 1053539..c2562fc 100644 --- a/src/interactive/app/eventloop.rs +++ b/src/interactive/app/eventloop.rs @@ -3,7 +3,7 @@ use crate::interactive::{ app::navigation::Navigation, state::FocussedPane, widgets::{glob_search, MainWindow, MainWindowProps}, - CursorDirection, CursorMode, DisplayOptions, MarkEntryMode, + CursorDirection, CursorMode, DisplayOptions, EntryCheck, MarkEntryMode, }; use anyhow::Result; use crossbeam::channel::Receiver; @@ -197,7 +197,7 @@ impl AppState { self.entries = tree_view.sorted_entries( self.navigation().view_root, self.sorting, - self.scan.is_some(), + self.entry_check(), ); if !self.received_events { @@ -217,6 +217,10 @@ impl AppState { self.reset_message(); // force "scanning" to appear } + pub(crate) fn entry_check(&self) -> EntryCheck { + EntryCheck::new(self.scan.is_some(), self.allow_entry_check) + } + fn process_terminal_event( &mut self, window: &mut MainWindow, @@ -443,7 +447,7 @@ impl AppState { self.entries = tree.sorted_entries( self.navigation().view_root, self.sorting, - self.scan.is_some(), + self.entry_check(), ); self.navigation_mut().selected = self.entries.first().map(|e| e.index); @@ -498,7 +502,7 @@ impl AppState { glob_tree_root: Some(tree_root), }; let new_entries = - glob_tree_view.sorted_entries(tree_root, self.sorting, self.scan.is_some()); + glob_tree_view.sorted_entries(tree_root, self.sorting, self.entry_check()); let new_entries = self .navigation_mut() @@ -553,7 +557,7 @@ impl AppState { self.entries = tree_view.sorted_entries( self.navigation().view_root, self.sorting, - self.scan.is_some(), + self.entry_check(), ); } } diff --git a/src/interactive/app/handlers.rs b/src/interactive/app/handlers.rs index 9639264..1d700c2 100644 --- a/src/interactive/app/handlers.rs +++ b/src/interactive/app/handlers.rs @@ -67,7 +67,7 @@ impl AppState { .map(|parent_idx| { ( parent_idx, - tree_view.sorted_entries(parent_idx, self.sorting, self.scan.is_some()), + tree_view.sorted_entries(parent_idx, self.sorting, self.entry_check()), ) }) } @@ -89,7 +89,7 @@ impl AppState { self.navigation().selected.map(|previously_selected| { ( previously_selected, - tree_view.sorted_entries(previously_selected, self.sorting, self.scan.is_some()), + tree_view.sorted_entries(previously_selected, self.sorting, self.entry_check()), ) }) } @@ -125,7 +125,7 @@ impl AppState { self.entries = tree_view.sorted_entries( self.navigation().view_root, self.sorting, - self.scan.is_some(), + self.entry_check(), ); } @@ -134,7 +134,7 @@ impl AppState { self.entries = tree_view.sorted_entries( self.navigation().view_root, self.sorting, - self.scan.is_some(), + self.entry_check(), ); } @@ -143,7 +143,7 @@ impl AppState { self.entries = tree_view.sorted_entries( self.navigation().view_root, self.sorting, - self.scan.is_some(), + self.entry_check(), ); } @@ -333,7 +333,7 @@ impl AppState { self.entries = tree_view.sorted_entries( self.navigation().view_root, self.sorting, - self.scan.is_some(), + self.entry_check(), ); } @@ -353,7 +353,7 @@ impl AppState { pub fn go_to_root(&mut self, tree_view: &TreeView<'_>) { let root = self.navigation().tree_root; - let entries = tree_view.sorted_entries(root, self.sorting, self.scan.is_some()); + let entries = tree_view.sorted_entries(root, self.sorting, self.entry_check()); self.navigation_mut().exit_node(root, &entries); self.entries = entries; } diff --git a/src/interactive/app/state.rs b/src/interactive/app/state.rs index 159e63e..3a0da28 100644 --- a/src/interactive/app/state.rs +++ b/src/interactive/app/state.rs @@ -44,6 +44,8 @@ pub struct AppState { pub walk_options: WalkOptions, /// The paths used in the initial traversal, at least 1. pub root_paths: Vec, + /// If true, listed entries will be validated for presence when switching directories. + pub allow_entry_check: bool, } impl AppState { @@ -61,6 +63,7 @@ impl AppState { stats: TraversalStats::default(), walk_options, root_paths: input, + allow_entry_check: true, } } } diff --git a/src/interactive/app/terminal.rs b/src/interactive/app/terminal.rs index a949bbd..16d7031 100644 --- a/src/interactive/app/terminal.rs +++ b/src/interactive/app/terminal.rs @@ -1,5 +1,6 @@ use std::path::PathBuf; +use crate::interactive::EntryCheck; use anyhow::Result; use crossbeam::channel::Receiver; use crosstermion::input::Event; @@ -28,6 +29,7 @@ impl TerminalApp { terminal: &mut Terminal, walk_options: WalkOptions, byte_format: ByteFormat, + entry_check: bool, input: Vec, ) -> Result where @@ -40,6 +42,7 @@ impl TerminalApp { let window = MainWindow::default(); let mut state = AppState::new(walk_options, input); + state.allow_entry_check = entry_check; let traversal = Traversal::new(); let stats = TraversalStats::default(); @@ -49,7 +52,7 @@ impl TerminalApp { state.navigation().view_root, state.sorting, state.glob_root(), - state.scan.is_some(), + EntryCheck::new(state.scan.is_some(), state.allow_entry_check), ); state.navigation_mut().selected = state.entries.first().map(|b| b.index); diff --git a/src/interactive/app/tests/utils.rs b/src/interactive/app/tests/utils.rs index 0dfd65a..a9907fb 100644 --- a/src/interactive/app/tests/utils.rs +++ b/src/interactive/app/tests/utils.rs @@ -198,8 +198,13 @@ pub fn initialized_app_and_terminal_with_closure( let (_key_send, key_receive) = crossbeam::channel::bounded(0); let input_paths = fixture_paths.iter().map(|c| convert(c.as_ref())).collect(); - let mut app = - TerminalApp::initialize(&mut terminal, walk_options, ByteFormat::Metric, input_paths)?; + let mut app = TerminalApp::initialize( + &mut terminal, + walk_options, + ByteFormat::Metric, + false, /* entry-check */ + input_paths, + )?; app.traverse()?; app.run_until_traversed(&mut terminal, key_receive)?; diff --git a/src/interactive/app/tree_view.rs b/src/interactive/app/tree_view.rs index 2afc366..5d74371 100644 --- a/src/interactive/app/tree_view.rs +++ b/src/interactive/app/tree_view.rs @@ -1,5 +1,5 @@ use super::{sorted_entries, EntryDataBundle, SortMode}; -use crate::interactive::path_of; +use crate::interactive::{path_of, EntryCheck}; use dua::traverse::{EntryData, Traversal, Tree, TreeIndex}; use petgraph::{visit::Bfs, Direction}; use std::path::{Path, PathBuf}; @@ -50,14 +50,14 @@ impl TreeView<'_> { &self, view_root: TreeIndex, sorting: SortMode, - is_scanning: bool, + check: EntryCheck, ) -> Vec { sorted_entries( &self.traversal.tree, view_root, sorting, self.glob_tree_root, - is_scanning, + check, ) } diff --git a/src/main.rs b/src/main.rs index 787d28e..fe8bd9a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -62,7 +62,10 @@ fn main() -> Result<()> { let res = match opt.command { #[cfg(feature = "tui-crossplatform")] - Some(Interactive { input }) => { + Some(Interactive { + no_entry_check, + input, + }) => { use anyhow::{anyhow, Context}; use crosstermion::terminal::{tui::new_terminal, AlternateRawScreen}; @@ -81,6 +84,7 @@ fn main() -> Result<()> { &mut terminal, walk_options, byte_format, + !no_entry_check, extract_paths_maybe_set_cwd(input, !opt.stay_on_filesystem)?, )?; app.traverse()?; diff --git a/src/options.rs b/src/options.rs index a59a90b..f2d5635 100644 --- a/src/options.rs +++ b/src/options.rs @@ -91,6 +91,9 @@ pub enum Command { #[cfg(feature = "tui-crossplatform")] #[clap(name = "interactive", visible_alias = "i")] Interactive { + /// Do not check entries for presence when listing a directory to avoid slugging performance on slow filesystems. + #[clap(long, short = 'e')] + no_entry_check: bool, /// One or more input files or directories. If unset, we will use all entries in the current working directory. #[clap(value_parser)] input: Vec, -- cgit v1.2.3