diff --git a/local/recipes/tui/tlc/source/src/filemanager/render.rs b/local/recipes/tui/tlc/source/src/filemanager/render.rs index 9a59ec103b..9fa16814c6 100644 --- a/local/recipes/tui/tlc/source/src/filemanager/render.rs +++ b/local/recipes/tui/tlc/source/src/filemanager/render.rs @@ -503,6 +503,27 @@ impl FileManager { )); } } + // Mini-histogram of file-size distribution across + // the directory. Each cell is the count of files in + // a log2 bucket. 10 cells cover 1B..1TB. + let histogram = size_histogram(panel); + let max_bucket = histogram.iter().copied().max().unwrap_or(1).max(1); + let hist_w = 10usize; + let mut hist_str = String::with_capacity(hist_w); + for &count in &histogram[..hist_w] { + let level = if count == 0 { + 0 + } else { + // Scale count/max to 0..8 (8 block-char levels). + ((count as u64 * 8 + max_bucket / 2) / max_bucket).min(8) + }; + hist_str.push(['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'][level as usize]); + } + footer_spans.push(Span::raw(" ")); + footer_spans.push(Span::styled( + hist_str, + Style::default().fg(self.theme.accent).bg(panel_bg), + )); } let total_w = footer_spans.iter().map(|s| s.width() as u16).sum::(); if total_w < inner.width { @@ -640,6 +661,28 @@ pub fn build_size_index_from_snap( snaps.iter().map(|(n, s, _)| (n.clone(), *s)).collect() } +/// Build a log2-bucketed file-size histogram (10 buckets covering +/// 1B..1TB). Skips directories. Returns 10 counts, one per +/// bucket (bucket 0 = 1B..1KiB, bucket 9 = ≥512GiB). +pub fn size_histogram(panel: &Panel) -> [u64; 10] { + let mut hist = [0u64; 10]; + for entry in panel.entries() { + if entry.is_dir() || entry.name == ".." { + continue; + } + let size = entry.stat.size; + if size == 0 { + hist[0] += 1; + continue; + } + // log2(size) for non-zero sizes, clamped to [0, 63]. + // Bucket 0 covers 0..1KiB, bucket 9 covers >=512GiB. + let bucket = (63 - size.leading_zeros() as usize).saturating_sub(9).min(9); + hist[bucket] += 1; + } + hist +} + /// Mark entries whose size differs from the index. Returns count marked. pub fn mark_differing_from_snap( panel: &mut Panel, @@ -843,4 +886,31 @@ pub fn quickview_lines(panel: &Panel) -> Vec { return vec!["(empty file)".to_string()]; } lines -} \ No newline at end of file +} +#[cfg(test)] +mod tests { + use super::*; + use crate::filemanager::panel::Panel; + use std::fs; + + #[test] + fn size_histogram_buckets_files_by_log2_size() { + let dir = std::env::temp_dir().join("tlc-fm-size-histogram"); + let _ = fs::remove_dir_all(&dir); + fs::create_dir_all(&dir).unwrap(); + // bucket 0: 1B..1KiB + fs::write(dir.join("small.txt"), b"hi").unwrap(); + // bucket 3: 8KiB..64KiB + let mut mid = Vec::with_capacity(16 * 1024); + mid.resize(16 * 1024, b'x'); + fs::write(dir.join("mid.bin"), &mid).unwrap(); + let panel = Panel::new(&dir, &Default::default()).unwrap(); + let hist = size_histogram(&panel); + let total: u64 = hist.iter().sum(); + assert_eq!(total, 2, "expected 2 regular files, got {hist:?}"); + // 16 KiB = 2^14 → log2(14) - 9 = bucket 5. + assert!(hist[5] >= 1, "16KiB file should land in bucket 5, got {hist:?}"); + assert!(hist[0] >= 1, "2B file should land in bucket 0"); + let _ = fs::remove_dir_all(&dir); + } +}