git.lucas.co / cce-files
file manager
git clone https://git.lucas.co/cce-files.git

src/util.rs (2.9K)

 1 //! Small formatting helpers shared across pages and services.
 2 
 3 /// Format a byte count as a short human-readable size (e.g. `1.5 M`).
 4 pub fn format_size(size: u64) -> String {
 5     if size < 1024 {
 6         format!("{} B", size)
 7     } else if size < 1024 * 1024 {
 8         format!("{:.1} K", size as f64 / 1024.0)
 9     } else if size < 1024 * 1024 * 1024 {
10         format!("{:.1} M", size as f64 / (1024.0 * 1024.0))
11     } else {
12         format!("{:.1} G", size as f64 / (1024.0 * 1024.0 * 1024.0))
13     }
14 }
15 
16 /// Format a Unix mode into a 10-character `drwxr-xr-x`-style permission string.
17 pub fn format_permissions(mode: u32) -> String {
18     let mut s = String::with_capacity(10);
19     s.push(if mode & 0o40000 != 0 { 'd' } else { '-' });
20     s.push(if mode & 0o400 != 0 { 'r' } else { '-' });
21     s.push(if mode & 0o200 != 0 { 'w' } else { '-' });
22     s.push(if mode & 0o100 != 0 { 'x' } else { '-' });
23     s.push(if mode & 0o040 != 0 { 'r' } else { '-' });
24     s.push(if mode & 0o020 != 0 { 'w' } else { '-' });
25     s.push(if mode & 0o010 != 0 { 'x' } else { '-' });
26     s.push(if mode & 0o004 != 0 { 'r' } else { '-' });
27     s.push(if mode & 0o002 != 0 { 'w' } else { '-' });
28     s.push(if mode & 0o001 != 0 { 'x' } else { '-' });
29     s
30 }
31 
32 #[cfg(test)]
33 mod tests {
34     use super::*;
35 
36     #[test]
37     fn format_size_units() {
38         assert_eq!(format_size(0), "0 B");
39         assert_eq!(format_size(512), "512 B");
40         assert_eq!(format_size(1024), "1.0 K");
41         assert_eq!(format_size(1048576), "1.0 M");
42         assert_eq!(format_size(1073741824), "1.0 G");
43     }
44 
45     #[test]
46     fn format_permissions_string() {
47         assert_eq!(format_permissions(0o40755), "drwxr-xr-x");
48         assert_eq!(format_permissions(0o100644), "-rw-r--r--");
49         assert_eq!(format_permissions(0o644), "-rw-r--r--");
50     }
51 }
52 
53 /// Truncate `s` to fit `avail` px, measured for real (resvg-backed, cached per
54 /// string+size — the handful of details strings re-measure only on selection
55 /// change). `head` replaces the front ("...ail/of/path"), else the back
56 /// ("name..."). Binary search on kept chars: ~7 probes for a long path.
57 pub fn truncate_px(s: &str, family: &str, size: f32, avail: f32, head: bool) -> String {
58     if cce_ui::widget::display::measure_text_width(s, family, size) <= avail {
59         return s.to_string();
60     }
61     let chars: Vec<char> = s.chars().collect();
62     let build = |keep: usize| -> String {
63         if head {
64             let tail: String = chars[chars.len() - keep..].iter().collect();
65             format!("...{tail}")
66         } else {
67             let kept: String = chars[..keep].iter().collect();
68             format!("{kept}...")
69         }
70     };
71     let (mut lo, mut hi) = (0usize, chars.len().saturating_sub(1));
72     while lo < hi {
73         let mid = (lo + hi + 1) / 2;
74         if cce_ui::widget::display::measure_text_width(&build(mid), family, size) <= avail {
75             lo = mid;
76         } else {
77             hi = mid - 1;
78         }
79     }
80     build(lo)
81 }