runa_tui/
formatter.rs

1use crate::file_manager::FileEntry;
2use std::collections::HashSet;
3use std::ffi::OsString;
4use std::sync::Arc;
5
6pub struct Formatter {
7    dirs_first: bool,
8    show_hidden: bool,
9    show_system: bool,
10    case_insensitive: bool,
11    always_show: Arc<HashSet<OsString>>,
12    always_show_lowercase: Arc<HashSet<String>>,
13    pane_width: usize,
14}
15
16impl Formatter {
17    pub fn new(
18        dirs_first: bool,
19        show_hidden: bool,
20        show_system: bool,
21        case_insensitive: bool,
22        always_show: Arc<HashSet<OsString>>,
23        pane_width: usize,
24    ) -> Self {
25        let always_show_lowercase = Arc::new(
26            always_show
27                .iter()
28                .map(|s| s.to_string_lossy().to_lowercase())
29                .collect::<HashSet<String>>(),
30        );
31        Self {
32            dirs_first,
33            show_hidden,
34            show_system,
35            case_insensitive,
36            always_show,
37            always_show_lowercase,
38            pane_width,
39        }
40    }
41
42    pub fn format(&self, entries: &mut [FileEntry]) {
43        // Sort the entries
44        entries.sort_by(|a, b| {
45            if self.dirs_first {
46                match (a.is_dir(), b.is_dir()) {
47                    (true, false) => return std::cmp::Ordering::Less,
48                    (false, true) => return std::cmp::Ordering::Greater,
49                    _ => {}
50                }
51            }
52            if self.case_insensitive {
53                a.lowercase_name().cmp(b.lowercase_name())
54            } else {
55                a.name_str().cmp(b.name_str())
56            }
57        });
58
59        // Apply pane_width to the display_name
60        for entry in entries.iter_mut() {
61            let suffix = if entry.is_dir() { "/" } else { "" };
62            let base_name = format!("{}{}", entry.name_str(), suffix);
63
64            let mut out = String::with_capacity(self.pane_width);
65            let mut current_w = 0;
66
67            for c in base_name.chars() {
68                // simple truncation for the main list
69                let w = unicode_width::UnicodeWidthChar::width(c).unwrap_or(0);
70                if current_w + w > self.pane_width {
71                    if !out.is_empty() {
72                        out.pop();
73                        out.push('…');
74                    }
75                    break;
76                }
77                out.push(c);
78                current_w += w;
79            }
80
81            if current_w < self.pane_width {
82                out.push_str(&" ".repeat(self.pane_width - current_w));
83            }
84            entry.set_display_name(out);
85        }
86    }
87
88    pub fn filter_entries(&self, entries: &mut Vec<FileEntry>) {
89        entries.retain(|e| {
90            let is_exception = if self.case_insensitive {
91                self.always_show_lowercase.contains(e.lowercase_name())
92            } else {
93                self.always_show.contains(e.name())
94            };
95
96            if is_exception {
97                return true;
98            }
99
100            let hidden_ok = self.show_hidden || !e.is_hidden();
101            let system_ok = self.show_system || !e.is_system();
102            hidden_ok && system_ok
103        });
104        self.format(entries);
105    }
106} // impl Formatter