diff options
author | Sebastian Thiel <sebastian.thiel@icloud.com> | 2024-01-17 10:20:05 +0100 |
---|---|---|
committer | Sebastian Thiel <sebastian.thiel@icloud.com> | 2024-01-17 10:20:05 +0100 |
commit | bed351ed2190e50e2932278b9b13b83c2969401b (patch) | |
tree | 808958e942afdb79d6105c93f5cdc045197c7a03 | |
parent | 1a54d95bd6e60bd5b071c772324c7a8540d250f6 (diff) | |
parent | 18a725dc5af97841afd06dcd4c8469e1d7ea873c (diff) |
feat: Press `r` or `R` for refresh (#96)
Lower-case `r` will refresh the currently selected entry, while upper-case `R`
will refresh the entire displayed directory, and all entries in it.
Further, what was called `item` is now called `entry` across the
user-interface.
-rw-r--r-- | README.md | 17 | ||||
-rw-r--r-- | src/aggregate.rs | 2 | ||||
-rw-r--r-- | src/common.rs | 3 | ||||
-rw-r--r-- | src/interactive/app/eventloop.rs | 189 | ||||
-rw-r--r-- | src/interactive/app/handlers.rs | 7 | ||||
-rw-r--r-- | src/interactive/app/state.rs | 33 | ||||
-rw-r--r-- | src/interactive/app/terminal.rs | 33 | ||||
-rw-r--r-- | src/interactive/app/tests/journeys_readonly.rs | 2 | ||||
-rw-r--r-- | src/interactive/app/tests/utils.rs | 12 | ||||
-rw-r--r-- | src/interactive/app/tree_view.rs | 40 | ||||
-rw-r--r-- | src/interactive/widgets/entries.rs | 26 | ||||
-rw-r--r-- | src/interactive/widgets/footer.rs | 9 | ||||
-rw-r--r-- | src/interactive/widgets/help.rs | 36 | ||||
-rw-r--r-- | src/interactive/widgets/main.rs | 2 | ||||
-rw-r--r-- | src/traverse.rs | 126 |
15 files changed, 382 insertions, 155 deletions
@@ -89,6 +89,23 @@ Via `pacman` on your ArchLinux system. sudo pacman -S dua-cli ``` +#### NixOS +https://search.nixos.org/packages?channel=23.11&show=dua&from=0&size=50&sort=relevance&type=packages&query=dua + +Nix-shell (temporary) + +``` +nix-shell -p dua +``` + +NixOS configuration + +``` + environment.systemPackages = [ + pkgs.dua + ]; +``` + #### NetBSD Via `pkgin` on your NetBSD system. diff --git a/src/aggregate.rs b/src/aggregate.rs index ec4d5ae..356a1a9 100644 --- a/src/aggregate.rs +++ b/src/aggregate.rs @@ -41,7 +41,7 @@ pub fn aggregate( continue; } }; - for entry in walk_options.iter_from_path(path.as_ref(), device_id) { + for entry in walk_options.iter_from_path(path.as_ref(), device_id, false) { stats.entries_traversed += 1; progress.throttled(|| { if let Some(err) = err.as_mut() { diff --git a/src/common.rs b/src/common.rs index 254fbbf..8bd0451 100644 --- a/src/common.rs +++ b/src/common.rs @@ -176,11 +176,12 @@ pub struct WalkOptions { type WalkDir = jwalk::WalkDirGeneric<((), Option<Result<std::fs::Metadata, jwalk::Error>>)>; impl WalkOptions { - pub fn iter_from_path(&self, root: &Path, root_device_id: u64) -> WalkDir { + pub fn iter_from_path(&self, root: &Path, root_device_id: u64, skip_root: bool) -> WalkDir { let ignore_dirs = self.ignore_dirs.clone(); let cwd = std::env::current_dir().unwrap_or_else(|_| root.to_owned()); WalkDir::new(root) .follow_links(false) + .min_depth(if skip_root { 1 } else { 0 }) .sort(match self.sorting { TraversalSorting::None => false, TraversalSorting::AlphabeticalByFileName => true, diff --git a/src/interactive/app/eventloop.rs b/src/interactive/app/eventloop.rs index 71dcb82..e7c91a4 100644 --- a/src/interactive/app/eventloop.rs +++ b/src/interactive/app/eventloop.rs @@ -1,6 +1,6 @@ +use crate::interactive::state::FilesystemScan; use crate::interactive::{ app::navigation::Navigation, - sorted_entries, state::FocussedPane, widgets::{glob_search, MainWindow, MainWindowProps}, CursorDirection, CursorMode, DisplayOptions, MarkEntryMode, @@ -10,8 +10,8 @@ use crossbeam::channel::Receiver; use crosstermion::crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; use crosstermion::input::Event; use dua::{ - traverse::{BackgroundTraversal, EntryData, Traversal}, - WalkOptions, WalkResult, + traverse::{BackgroundTraversal, EntryData, Traversal, TreeIndex}, + WalkResult, }; use std::path::PathBuf; use tui::backend::Backend; @@ -43,10 +43,10 @@ impl AppState { { let props = MainWindowProps { 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, + entries_traversed: self.stats.entries_traversed, + total_bytes: tree_view.total_size(), + start: self.stats.start, + elapsed: self.stats.elapsed, display, state: self, }; @@ -64,19 +64,27 @@ impl AppState { result } - pub fn traverse( - &mut self, - traversal: &Traversal, - walk_options: &WalkOptions, - input: Vec<PathBuf>, - ) -> Result<()> { - let background_traversal = - BackgroundTraversal::start(traversal.root_index, walk_options, input)?; + pub fn traverse(&mut self, traversal: &Traversal, input: Vec<PathBuf>) -> Result<()> { + let traverasal = BackgroundTraversal::start( + traversal.root_index, + &self.walk_options, + input, + false, + true, + )?; self.navigation_mut().view_root = traversal.root_index; - self.active_traversal = Some(background_traversal); + self.scan = Some(FilesystemScan { + active_traversal: traverasal, + previous_selection: None, + }); Ok(()) } + fn recompute_sizes_recursively(&mut self, traversal: &mut Traversal, node_index: TreeIndex) { + let mut tree_view = self.tree_view(traversal); + tree_view.recompute_sizes_recursively(node_index); + } + fn refresh_screen<B>( &mut self, window: &mut MainWindow, @@ -126,11 +134,15 @@ impl AppState { where B: Backend, { - if let Some(active_traversal) = &mut self.active_traversal { + if let Some(FilesystemScan { + active_traversal, + previous_selection, + }) = self.scan.as_mut() + { crossbeam::select! { recv(events) -> event => { let Ok(event) = event else { - return Ok(Some(WalkResult { num_errors: 0 })); + return Ok(Some(WalkResult { num_errors: self.stats.io_errors })); }; let res = self.process_terminal_event( window, @@ -148,17 +160,23 @@ impl AppState { }; if let Some(is_finished) = active_traversal.integrate_traversal_event(traversal, event) { + self.stats = active_traversal.stats; + let previous_selection = previous_selection.clone(); if is_finished { - self.active_traversal = None; + let root_index = active_traversal.root_idx; + self.recompute_sizes_recursively(traversal, root_index); + self.scan = None; } - self.update_state(traversal); + self.update_state_during_traversal(traversal, previous_selection.as_ref(), is_finished); self.refresh_screen(window, traversal, display, terminal)?; }; } } } else { let Ok(event) = events.recv() else { - return Ok(Some(WalkResult { num_errors: 0 })); + return Ok(Some(WalkResult { + num_errors: self.stats.io_errors, + })); }; let result = self.process_terminal_event(window, traversal, display, terminal, event)?; @@ -169,15 +187,28 @@ impl AppState { Ok(None) } - fn update_state(&mut self, traversal: &Traversal) { - self.entries = sorted_entries( - &traversal.tree, - self.navigation().view_root, - self.sorting, - self.glob_root(), - ); + fn update_state_during_traversal( + &mut self, + traversal: &mut Traversal, + previous_selection: Option<&(PathBuf, usize)>, + is_finished: bool, + ) { + let tree_view = self.tree_view(traversal); + self.entries = tree_view.sorted_entries(self.navigation().view_root, self.sorting); + if !self.received_events { - self.navigation_mut().selected = self.entries.first().map(|b| b.index); + let previously_selected_entry = + previous_selection.and_then(|(selected_name, selected_idx)| { + self.entries + .iter() + .find(|e| e.name == *selected_name) + .or_else(|| self.entries.get(*selected_idx)) + }); + if let Some(selected_entry) = previously_selected_entry { + self.navigation_mut().selected = Some(selected_entry.index); + } else if is_finished { + self.navigation_mut().selected = self.entries.first().map(|b| b.index); + } } self.reset_message(); // force "scanning" to appear } @@ -222,12 +253,16 @@ impl AppState { self.cycle_focus(window); } Char('/') if !glob_focussed => { - self.toggle_glob_search(window); + if self.scan.is_some() { + self.message = Some("glob search disabled during traversal".into()); + } else { + self.toggle_glob_search(window); + } } Char('?') if !glob_focussed => self.toggle_help_pane(window), Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) && !glob_focussed => { return Ok(Some(WalkResult { - num_errors: tree_view.traversal.io_errors, + num_errors: self.stats.io_errors, })) } Char('q') if !glob_focussed => { @@ -275,6 +310,8 @@ impl AppState { Char('o') | Char('l') | Enter | Right => { self.enter_node_with_traversal(&tree_view) } + Char('r') => self.refresh(&mut tree_view, window, Refresh::Selected)?, + Char('R') => self.refresh(&mut tree_view, window, Refresh::AllInView)?, Char('H') | Home => self.change_entry_selection(CursorDirection::ToTop), Char('G') | End => self.change_entry_selection(CursorDirection::ToBottom), PageUp => self.change_entry_selection(CursorDirection::PageUp), @@ -311,6 +348,81 @@ impl AppState { Ok(None) } + fn refresh( + &mut self, + tree: &mut TreeView<'_>, + window: &mut MainWindow, + what: Refresh, + ) -> anyhow::Result<()> { + // If another traversal is already running do not do anything. + if self.scan.is_some() { + self.message = Some("Traversal already running".into()); + return Ok(()); + } + + let previous_selection = self.navigation().selected.and_then(|sel_index| { + tree.tree().node_weight(sel_index).map(|w| { + ( + w.name.clone(), + self.entries + .iter() + .enumerate() + .find_map(|(idx, e)| (e.index == sel_index).then_some(idx)) + .expect("selected item is always in entries"), + ) + }) + }); + + // If we are displaying the root of the glob search results then cancel the search. + if let Some(glob_tree_root) = tree.glob_tree_root { + if glob_tree_root == self.navigation().view_root { + self.quit_glob_mode(tree, window) + } + } + + let (remove_root_node, skip_root, index, parent_index) = match what { + Refresh::Selected => { + let Some(selected) = self.navigation().selected else { + return Ok(()); + }; + let parent_index = tree + .fs_parent_of(selected) + .expect("there is always a parent to a selection"); + (true, false, selected, parent_index) + } + Refresh::AllInView => ( + false, + true, + self.navigation().view_root, + self.navigation().view_root, + ), + }; + + let mut path = tree.path_of(index); + if path.to_str() == Some("") { + path = PathBuf::from("."); + } + tree.remove_entries(index, remove_root_node); + tree.recompute_sizes_recursively(parent_index); + + self.entries = tree.sorted_entries(self.navigation().view_root, self.sorting); + self.navigation_mut().selected = self.entries.first().map(|e| e.index); + + self.scan = Some(FilesystemScan { + active_traversal: BackgroundTraversal::start( + parent_index, + &self.walk_options, + vec![path], + skip_root, + false, + )?, + previous_selection, + }); + + self.received_events = false; + Ok(()) + } + fn tree_view<'a>(&mut self, traversal: &'a mut Traversal) -> TreeView<'a> { TreeView { traversal, @@ -369,10 +481,10 @@ impl AppState { match self.focussed { Main => { if self.glob_navigation.is_some() { - self.handle_glob_quit(tree_view, window); + self.quit_glob_mode(tree_view, window); } else { return Some(Ok(WalkResult { - num_errors: tree_view.traversal.io_errors, + num_errors: self.stats.io_errors, })); } } @@ -382,13 +494,13 @@ impl AppState { window.help_pane = None } Glob => { - self.handle_glob_quit(tree_view, window); + self.quit_glob_mode(tree_view, window); } } None } - fn handle_glob_quit(&mut self, tree_view: &mut TreeView<'_>, window: &mut MainWindow) { + fn quit_glob_mode(&mut self, tree_view: &mut TreeView<'_>, window: &mut MainWindow) { use FocussedPane::*; self.focussed = Main; if let Some(glob_source) = &self.glob_navigation { @@ -402,6 +514,13 @@ impl AppState { } } +enum Refresh { + /// Refresh the directory currently in view + AllInView, + /// Refresh only the selected item + Selected, +} + pub fn draw_window<B>( window: &mut MainWindow, props: MainWindowProps<'_>, diff --git a/src/interactive/app/handlers.rs b/src/interactive/app/handlers.rs index 754fa51..c320c59 100644 --- a/src/interactive/app/handlers.rs +++ b/src/interactive/app/handlers.rs @@ -162,7 +162,7 @@ impl AppState { } pub fn reset_message(&mut self) { - if self.active_traversal.is_some() { + if self.scan.is_some() { self.message = Some("-> scanning <-".into()); } else { self.message = None; @@ -312,7 +312,8 @@ impl AppState { let parent_idx = tree_view .fs_parent_of(index) .expect("us being unable to delete the root index"); - let entries_deleted = tree_view.remove_entries(index); + let entries_deleted = + tree_view.remove_entries(index, true /* remove node at `index` */); if !tree_view.exists(self.navigation().view_root) { self.go_to_root(tree_view); @@ -334,7 +335,7 @@ impl AppState { entries_deleted } - fn go_to_root(&mut self, tree_view: &TreeView<'_>) { + 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.navigation_mut().exit_node(root, &entries); diff --git a/src/interactive/app/state.rs b/src/interactive/app/state.rs index b303531..aee7542 100644 --- a/src/interactive/app/state.rs +++ b/src/interactive/app/state.rs @@ -1,6 +1,8 @@ use std::collections::HashSet; +use std::path::PathBuf; -use dua::traverse::BackgroundTraversal; +use dua::traverse::{BackgroundTraversal, TraversalStats}; +use dua::WalkOptions; use crate::interactive::widgets::Column; @@ -22,7 +24,12 @@ pub struct Cursor { pub y: u16, } -#[derive(Default)] +pub(crate) struct FilesystemScan { + pub active_traversal: BackgroundTraversal, + /// The selected item prior to starting the traversal, if available, based on its name or index into [`AppState::entries`]. + pub previous_selection: Option<(PathBuf, usize)>, +} + pub struct AppState { pub navigation: Navigation, pub glob_navigation: Option<Navigation>, @@ -32,5 +39,25 @@ pub struct AppState { pub message: Option<String>, pub focussed: FocussedPane, pub received_events: bool, - pub active_traversal: Option<BackgroundTraversal>, + pub scan: Option<FilesystemScan>, + pub stats: TraversalStats, + pub walk_options: WalkOptions, +} + +impl AppState { + pub fn new(walk_options: WalkOptions) -> Self { + AppState { + navigation: Default::default(), + glob_navigation: None, + entries: vec![], + sorting: Default::default(), + show_columns: Default::default(), + message: None, + focussed: Default::default(), + received_events: false, + scan: None, + stats: TraversalStats::default(), + walk_options, + } + } } diff --git a/src/interactive/app/terminal.rs b/src/interactive/app/terminal.rs index 2109e00..f29834f 100644 --- a/src/interactive/app/terminal.rs +++ b/src/interactive/app/terminal.rs @@ -4,7 +4,7 @@ use anyhow::Result; use crossbeam::channel::Receiver; use crosstermion::input::Event; use dua::{ - traverse::{EntryData, Traversal, Tree}, + traverse::{Traversal, TraversalStats}, ByteFormat, WalkOptions, WalkResult, }; use tui::prelude::Backend; @@ -17,10 +17,10 @@ use super::{sorted_entries, state::AppState, DisplayOptions}; /// State and methods representing the interactive disk usage analyser for the terminal pub struct TerminalApp { pub traversal: Traversal, + pub stats: TraversalStats, pub display: DisplayOptions, pub state: AppState, pub window: MainWindow, - pub walk_options: WalkOptions, } impl TerminalApp { @@ -38,21 +38,9 @@ impl TerminalApp { let display = DisplayOptions::new(byte_format); let window = MainWindow::default(); - let mut state = AppState::default(); - - let traversal = { - let mut tree = Tree::new(); - let root_index = tree.add_node(EntryData::default()); - Traversal { - tree, - root_index, - entries_traversed: 0, - start: std::time::Instant::now(), - elapsed: None, - io_errors: 0, - total_bytes: None, - } - }; + let mut state = AppState::new(walk_options); + let traversal = Traversal::new(); + let stats = TraversalStats::default(); state.navigation_mut().view_root = traversal.root_index; state.entries = sorted_entries( @@ -67,15 +55,14 @@ impl TerminalApp { state, display, traversal, + stats, window, - walk_options, }; Ok(app) } pub fn traverse(&mut self, input: Vec<PathBuf>) -> Result<()> { - self.state - .traverse(&self.traversal, &self.walk_options, input)?; + self.state.traverse(&self.traversal, input)?; Ok(()) } @@ -112,7 +99,7 @@ mod tests { where B: Backend, { - while self.state.active_traversal.is_some() { + while self.state.scan.is_some() { if let Some(res) = self.state.process_event( &mut self.window, &mut self.traversal, @@ -123,7 +110,9 @@ mod tests { return Ok(res); } } - Ok(WalkResult { num_errors: 0 }) + Ok(WalkResult { + num_errors: self.stats.io_errors, + }) } } } diff --git a/src/interactive/app/tests/journeys_readonly.rs b/src/interactive/app/tests/journeys_readonly.rs index b6dc579..f307294 100644 --- a/src/interactive/app/tests/journeys_readonly.rs +++ b/src/interactive/app/tests/journeys_readonly.rs @@ -40,7 +40,7 @@ fn simple_user_journey_read_only() -> Result<()> { ); assert!( - app.state.active_traversal.is_none(), + app.state.scan.is_none(), "it will not think it is still scanning as there is no traversal" ); diff --git a/src/interactive/app/tests/utils.rs b/src/interactive/app/tests/utils.rs index a54fa8f..b075e35 100644 --- a/src/interactive/app/tests/utils.rs +++ b/src/interactive/app/tests/utils.rs @@ -234,9 +234,9 @@ pub fn sample_01_tree() -> Tree { let root_size = 1259070; #[cfg(windows)] let root_size = 1259069; - let rn = add_node("", root_size, 14, None); + let rn = add_node("", root_size, 10, None); { - let sn = add_node(&fixture_str("sample-01"), root_size, 13, Some(rn)); + let sn = add_node(&fixture_str("sample-01"), root_size, 10, Some(rn)); { add_node(".hidden.666", 666, 0, Some(sn)); add_node("a", 256, 0, Some(sn)); @@ -245,7 +245,7 @@ pub fn sample_01_tree() -> Tree { add_node("c.lnk", 1, 0, Some(sn)); #[cfg(windows)] add_node("c.lnk", 0, 0, Some(sn)); - let dn = add_node("dir", 1258024, 7, Some(sn)); + let dn = add_node("dir", 1258024, 5, Some(sn)); { add_node("1000bytes", 1000, 0, Some(dn)); add_node("dir-a.1mb", 1_000_000, 0, Some(dn)); @@ -272,7 +272,7 @@ pub fn sample_02_tree(use_native_separator: bool) -> (Tree, TreeIndex) { { let mut add_node = make_add_node(&mut tree); let root_size = 1540; - root_index = add_node("", root_size, 10, None); + root_index = add_node("", root_size, 6, None); { let sn = add_node( format!( @@ -285,13 +285,13 @@ pub fn sample_02_tree(use_native_separator: bool) -> (Tree, TreeIndex) { ) .as_str(), root_size, - 9, + 6, Some(root_index), ); { add_node("a", 256, 0, Some(sn)); add_node("b", 1, 0, Some(sn)); - let dn = add_node("dir", 1283, 6, Some(sn)); + let dn = add_node("dir", 1283, 4, Some(sn)); { add_node("c", 257, 0, Some(dn)); add_node("d", 2, 0, Some(dn)); diff --git a/src/interactive/app/tree_view.rs b/src/interactive/app/tree_view.rs index 74ec4fa..8a80cf0 100644 --- a/src/interactive/app/tree_view.rs +++ b/src/interactive/app/tree_view.rs @@ -59,13 +59,15 @@ impl TreeView<'_> { current_path(&self.traversal.tree, view_root, self.glob_tree_root) } - pub fn remove_entries(&mut self, index: TreeIndex) -> usize { + pub fn remove_entries(&mut self, root_index: TreeIndex, remove_root_node: bool) -> usize { let mut entries_deleted = 0; - let mut bfs = Bfs::new(self.tree(), index); + let mut bfs = Bfs::new(self.tree(), root_index); while let Some(nx) = bfs.next(&self.tree()) { + if nx == root_index && !remove_root_node { + continue; + } self.tree_mut().remove_node(nx); - self.traversal.entries_traversed -= 1; entries_deleted += 1; } entries_deleted @@ -75,28 +77,40 @@ impl TreeView<'_> { self.tree().node_weight(idx).is_some() } + pub fn total_size(&self) -> u128 { + self.tree() + .neighbors_directed(self.traversal.root_index, Direction::Outgoing) + .filter_map(|idx| self.tree().node_weight(idx).map(|w| w.size)) + .sum() + } + pub fn recompute_sizes_recursively(&mut self, mut index: TreeIndex) { loop { - let size_of_children = self + let (size_of_children, item_count) = self .tree() .neighbors_directed(index, Direction::Outgoing) - .filter_map(|idx| self.tree().node_weight(idx).map(|w| w.size)) - .sum(); - self.traversal + .filter_map(|idx| { + self.tree() + .node_weight(idx) + .map(|w| (w.size, w.entry_count.unwrap_or(1))) + }) + .reduce(|a, b| (a.0 + b.0, a.1 + b.1)) + .unwrap_or_default(); + + let node = self + .traversal .tree .node_weight_mut(index) - .expect("valid index") - .size = size_of_children; + .expect("valid index"); + + node.size = size_of_children; + node.entry_count = Some(item_count); match self.fs_parent_of(index) { None => break, Some(parent) => index = parent, } } - self.traversal.total_bytes = self - .tree() - .node_weight(self.traversal.root_index) - .map(|w| w.size); } } diff --git a/src/interactive/widgets/entries.rs b/src/interactive/widgets/entries.rs index 45e070c..c4794f4 100644 --- a/src/interactive/widgets/entries.rs +++ b/src/interactive/widgets/entries.rs @@ -63,12 +63,18 @@ impl Entries { let list = &mut self.list; let total: u128 = entries.iter().map(|b| b.size).sum(); - let (item_count, item_size): (u64, u128) = entries + let (recursive_item_count, item_size): (u64, u128) = entries .iter() .map(|f| (f.entry_count.unwrap_or(1), f.size)) .reduce(|a, b| (a.0 + b.0, a.1 + b.1)) .unwrap_or_default(); - let title = title(current_path, item_count, *display, item_size); + let title = title( + current_path, + entries.len(), + recursive_item_count, + *display, + item_size, + ); let title_block = title_block(&title, *border_style); let inner_area = title_block.inner(area); let entry_in_view = entry_in_view(*selected, entries); @@ -155,15 +161,17 @@ fn title_block(title: &str, border_style: Style) -> Block<'_> { .borders(Borders::ALL) } -fn title(current_path: &str, item_count: u64, display: DisplayOptions, size: u128) -> String { +fn title( + current_path: &str, + item_count: usize, + recursive_item_count: u64, + display: DisplayOptions, + size: u128, +) -> String { format!( - " {} ({} item{}, {}) ", + " {} ({item_count} visible, {} total, {}) ", current_path, - COUNT.format(item_count as f64), - match item_count { - 1 => "", - _ => "s", - }, + COUNT.format(recursive_item_count as f64), display.byte_format.display(size) ) } diff --git a/src/interactive/widgets/footer.rs b/src/interactive/widgets/footer.rs index a91fdb6..80bd352 100644 --- a/src/interactive/widgets/footer.rs +++ b/src/interactive/widgets/footer.rs @@ -14,7 +14,7 @@ use crate::interactive::SortMode; pub struct Footer; pub struct FooterProps { - pub total_bytes: Option<u128>, + pub total_bytes: u128, pub entries_traversed: u64, pub traversal_start: std::time::Instant, pub elapsed: Option<std::time::Duration>, @@ -37,7 +37,7 @@ impl Footer { let spans = vec![ Span::from(format!( - "Sort mode: {} Total disk usage: {} Items: {} {progress} ", + "Sort mode: {} Total disk usage: {} Processed {} entries {progress} ", match sort_mode { SortMode::SizeAscending => "size ascending", SortMode::SizeDescending => "size descending", @@ -46,10 +46,7 @@ impl Footer { SortMode::CountAscending => "items ascending", SortMode::CountDescending => "items descending", }, - match total_bytes { - Some(b) => format!("{}", format.display(*b)), - None => "-".to_owned(), - }, + format.display(*total_bytes), entries_traversed, progress = match elapsed { Some(elapsed) => format!("in {:.02}s", elapsed.as_secs_f32()), diff --git a/src/interactive/widgets/help.rs b/src/interactive/widgets/help.rs index d5a481e..8cf83f6 100644 --- a/src/interactive/widgets/help.rs +++ b/src/interactive/widgets/help.rs @@ -123,8 +123,8 @@ impl HelpPane { } title("Navigation"); { - hotkey("j/<Down>", "Move down 1 item.", None); - hotkey("k/<Up>", "Move up 1 item.", None); + hotkey("j/<Down>", "Move down 1 entry.", None); + hotkey("k/<Up>", "Move up 1 entry.", None); hotkey("o/l/<Enter>", "Descent into the selected directory.", None); hotkey("<Right>", "^", None); hotkey( @@ -133,9 +133,9 @@ impl HelpPane { None, ); hotkey("<Backspace>", "^", None); - hotkey("Ctrl + d", "Move down 10 items.", None); + hotkey("Ctrl + d", "Move down 10 entries.", None); hotkey("<Page Down>", "^", None); - hotkey("Ctrl + u", "Move up 10 items.", None); + hotkey("Ctrl + u", "Move up 10 entries.", None); hotkey("<Page Up>", "^", None); hotkey("H/<Home>", "Move to the top of the list.", None); hotkey("G/<End>", "Move to the bottom of the list.", None); @@ -150,8 +150,8 @@ impl HelpPane { None, ); hotkey("M", "Show/hide modified time.", None); - hotkey("c", "Toggle sort by items descending/ascending.", None); - hotkey("C", "Show/hide item count.", None); + hotkey("c", "Toggle sort by entries descending/ascending.", None); + hotkey("C", "Show/hide entry count.", None); hotkey( "g/S", "Cycle through percentage display and bar options.", @@ -163,46 +163,48 @@ impl HelpPane { { hotkey( "Shift + o", - "Open the selected item with the associated program.", + "Open the selected entry with the associated program.", None, ); hotkey( "d", - "Toggle the currently selected item and move down.", + "Toggle the currently selected entry and move down.", None, ); hotkey( "x", - "Mark the currently selected item for deletion and move down.", + "Mark the currently selected entry for deletion and move down.", None, ); - hotkey("<Space>", "Toggle the currently selected item.", None); - hotkey("a", "Toggle all items.", None); + hotkey("<Space>", "Toggle the currently selected entry.", None); + hotkey("a", "Toggle all entries.", None); hotkey( "/", "Git-style glob search, case-insensitive.", Some("Search starts from the current directory."), ); + hotkey("r", "Refresh only the selected entry.", None); + hotkey("R", "Refresh all entries in the current view.", None); spacer(); } - title("Mark items pane"); + title("Mark entries pane"); { hotkey( "x/d/<Space>", |