summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSebastian Thiel <sebastian.thiel@icloud.com>2024-01-17 10:20:05 +0100
committerSebastian Thiel <sebastian.thiel@icloud.com>2024-01-17 10:20:05 +0100
commitbed351ed2190e50e2932278b9b13b83c2969401b (patch)
tree808958e942afdb79d6105c93f5cdc045197c7a03
parent1a54d95bd6e60bd5b071c772324c7a8540d250f6 (diff)
parent18a725dc5af97841afd06dcd4c8469e1d7ea873c (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.md17
-rw-r--r--src/aggregate.rs2
-rw-r--r--src/common.rs3
-rw-r--r--src/interactive/app/eventloop.rs189
-rw-r--r--src/interactive/app/handlers.rs7
-rw-r--r--src/interactive/app/state.rs33
-rw-r--r--src/interactive/app/terminal.rs33
-rw-r--r--src/interactive/app/tests/journeys_readonly.rs2
-rw-r--r--src/interactive/app/tests/utils.rs12
-rw-r--r--src/interactive/app/tree_view.rs40
-rw-r--r--src/interactive/widgets/entries.rs26
-rw-r--r--src/interactive/widgets/footer.rs9
-rw-r--r--src/interactive/widgets/help.rs36
-rw-r--r--src/interactive/widgets/main.rs2
-rw-r--r--src/traverse.rs126
15 files changed, 382 insertions, 155 deletions
diff --git a/README.md b/README.md
index 0049899..6e2b3ff 100644
--- a/README.md
+++ b/README.md
@@ -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>",