tlc: Sprint 3 C3 — delete dialog shows total recursive size

DeleteDialog header now includes the total recursive size of
all paths to be deleted (formatted via format_size). For example:
  'Delete 3 (1.2 MB) ?'  instead of just 'Delete ?'.
Computed eagerly at construction via crate::ops::count_bytes so
render() does not block on filesystem traversal.

DeleteDialog (delete_dialog.rs):
  - New field total_bytes: u64 (cached at construction)
  - new() calls crate::ops::count_bytes(&paths) to populate it
  - render() prepends '(N items)' to header and appends size when > 0

Tests (3 new in delete_dialog::tests):
  - new_computes_total_bytes_for_directory (recursive dir sum)
  - new_total_bytes_zero_for_missing_paths
  - new_total_bytes_sums_multiple_paths

Total: 1271 passing (was 1268; +3 new).
This commit is contained in:
kellito
2026-07-05 17:50:09 +03:00
parent 9ea6826a58
commit 8ca31beee3
@@ -25,6 +25,10 @@ use crate::widget::button::{ButtonKind, ButtonSpec};
pub struct DeleteDialog {
/// Paths to be deleted.
pub paths: Vec<PathBuf>,
/// Cached total size in bytes (recursive sum across all paths).
/// Computed at construction via `crate::ops::count_bytes` so the
/// dialog renders without blocking on filesystem traversal.
pub total_bytes: u64,
/// True after Y confirms.
pub confirmed: bool,
/// True after N / Esc cancels.
@@ -36,11 +40,16 @@ pub struct DeleteDialog {
}
impl DeleteDialog {
/// Create a new delete dialog for the given paths.
/// Create a new delete dialog for the given paths. The total
/// recursive size is computed eagerly (filesystem walk) so the
/// dialog header can display "Delete N items (X MB)?" without
/// blocking at render time.
#[must_use]
pub fn new(paths: Vec<PathBuf>) -> Self {
let total_bytes = crate::ops::count_bytes(&paths);
Self {
paths,
total_bytes,
confirmed: false,
cancelled: false,
width_pct: 0.5,
@@ -105,7 +114,16 @@ impl DeleteDialog {
])
.split(inner);
let header_text = format!("{} ?", crate::locale::t("dialog_title_delete"));
let header_text = if self.total_bytes > 0 {
format!(
"{} {} ({} ?)",
crate::locale::t("dialog_title_delete"),
self.paths.len(),
crate::filemanager::format_utils::format_size(self.total_bytes)
)
} else {
format!("{} {} ?", crate::locale::t("dialog_title_delete"), self.paths.len())
};
let header = Line::from(Span::styled(
header_text,
Style::default()
@@ -207,4 +225,34 @@ mod tests {
assert!(d.is_cancelled());
assert!(!d.is_confirmed());
}
#[test]
fn new_computes_total_bytes_for_directory() {
let dir = tempfile::tempdir().unwrap();
let sub = dir.path().join("sub");
std::fs::create_dir(&sub).unwrap();
std::fs::write(sub.join("a.txt"), b"hello").unwrap();
std::fs::write(sub.join("b.txt"), b"world!!").unwrap();
let d = DeleteDialog::new(vec![dir.path().to_path_buf()]);
// Recursive sum: 5 + 7 = 12 bytes (directories don't count
// their own entries).
assert_eq!(d.total_bytes, 12);
}
#[test]
fn new_total_bytes_zero_for_missing_paths() {
let d = DeleteDialog::new(vec![std::path::PathBuf::from("/no/such/path/xyz")]);
assert_eq!(d.total_bytes, 0);
}
#[test]
fn new_total_bytes_sums_multiple_paths() {
let dir = tempfile::tempdir().unwrap();
let p1 = dir.path().join("a");
let p2 = dir.path().join("b");
std::fs::write(&p1, b"12345").unwrap();
std::fs::write(&p2, b"678").unwrap();
let d = DeleteDialog::new(vec![p1, p2]);
assert_eq!(d.total_bytes, 8);
}
}