W37: Size distribution histogram in panel footer
Add a 10-cell log2-bucketed histogram showing file-size distribution across the directory. Each cell is the count of files in a bucket (1B..1KiB, 1KiB..2KiB, ... >=512GiB) scaled to a 0..8 block-char level. Rendered in accent colour next to the existing per-file size bar. - size_histogram() returns [u64; 10] counts per bucket - Uses u64 leading_zeros trick for O(1) log2 size - Skips directories and the '..' entry - Directories are excluded from the histogram - Renders after the cursor file's size bar Added 1 unit test verifying bucket placement for 2B and 16KiB. Tests: 1400 pass (was 1399), zero warnings.
This commit is contained in:
@@ -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::<u16>();
|
||||
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<String> {
|
||||
return vec!["(empty file)".to_string()];
|
||||
}
|
||||
lines
|
||||
}
|
||||
}
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user