lx_cli/formatter/
short.rs

1use crate::config::Config;
2use crate::file_entry::{FileEntry, FileType};
3use crate::sort::sort_default;
4use colored::Colorize;
5use unicode_width::UnicodeWidthStr;
6
7pub fn format_short(mut entries: Vec<FileEntry>, config: &Config) {
8    // Apply default sorting: by type, then alphabetically (case-insensitive)
9    sort_default(&mut entries);
10
11    let mut directories: Vec<FileEntry> = Vec::new();
12    let mut executables: Vec<FileEntry> = Vec::new();
13    let mut regular_files: Vec<FileEntry> = Vec::new();
14
15    for entry in entries {
16        match entry.get_file_type() {
17            FileType::Directory => directories.push(entry),
18            FileType::Executable => executables.push(entry),
19            FileType::RegularFile => regular_files.push(entry),
20        }
21    }
22
23    let column_spacing = config.display.column_spacing;
24    let max_rows = config.display.max_rows;
25
26    // If max_rows is set (not 0), format each file type with wrapping
27    // Otherwise, use the original single-column-per-type format
28    if max_rows > 0 {
29        format_with_max_rows(
30            directories,
31            executables,
32            regular_files,
33            max_rows,
34            column_spacing,
35            config,
36        );
37    } else {
38        format_single_column_per_type(
39            directories,
40            executables,
41            regular_files,
42            column_spacing,
43            config,
44        );
45    }
46}
47
48fn format_with_max_rows(
49    directories: Vec<FileEntry>,
50    executables: Vec<FileEntry>,
51    regular_files: Vec<FileEntry>,
52    max_rows: usize,
53    column_spacing: usize,
54    config: &Config,
55) {
56    // Calculate how many columns needed for each file type
57    let dir_num_cols = if directories.is_empty() {
58        0
59    } else {
60        directories.len().div_ceil(max_rows)
61    };
62
63    let exec_num_cols = if executables.is_empty() {
64        0
65    } else {
66        executables.len().div_ceil(max_rows)
67    };
68
69    let file_num_cols = if regular_files.is_empty() {
70        0
71    } else {
72        regular_files.len().div_ceil(max_rows)
73    };
74
75    // Calculate width for each file type
76    let dir_width = directories
77        .iter()
78        .map(|e| {
79            let filename = e.path.to_string_lossy();
80            UnicodeWidthStr::width(e.get_icon().as_str())
81                + 1
82                + UnicodeWidthStr::width(filename.as_ref())
83        })
84        .max()
85        .unwrap_or(0);
86
87    let exec_width = executables
88        .iter()
89        .map(|e| {
90            let filename = e.path.to_string_lossy();
91            UnicodeWidthStr::width(e.get_icon().as_str())
92                + 1
93                + UnicodeWidthStr::width(filename.as_ref())
94        })
95        .max()
96        .unwrap_or(0);
97
98    let file_width = regular_files
99        .iter()
100        .map(|e| {
101            let filename = e.path.to_string_lossy();
102            UnicodeWidthStr::width(e.get_icon().as_str())
103                + 1
104                + UnicodeWidthStr::width(filename.as_ref())
105        })
106        .max()
107        .unwrap_or(0);
108
109    // Print rows, with all file types side-by-side
110    for row in 0..max_rows {
111        let mut line = String::new();
112        let mut has_any_content = false;
113
114        // Directories section
115        if dir_num_cols > 0 {
116            for col in 0..dir_num_cols {
117                let idx = col * max_rows + row;
118                if idx < directories.len() {
119                    if col > 0 {
120                        line.push_str(&" ".repeat(column_spacing));
121                    }
122
123                    let entry = &directories[idx];
124                    let filename = entry.path.to_string_lossy();
125                    let icon = entry.get_icon_custom(&config.icons);
126                    let actual_width = UnicodeWidthStr::width(icon.as_str())
127                        + 1
128                        + UnicodeWidthStr::width(filename.as_ref());
129
130                    line.push_str(&format!(
131                        "{} {}",
132                        icon.color(entry.get_icon_color(&config.icons.colors)),
133                        filename.color(entry.get_color(&config.colors)).bold()
134                    ));
135
136                    if actual_width < dir_width {
137                        line.push_str(&" ".repeat(dir_width - actual_width));
138                    }
139                    has_any_content = true;
140                } else if col == 0 {
141                    // Empty row in directories section, but still need spacing for alignment
142                    line.push_str(&" ".repeat(dir_width));
143                } else {
144                    line.push_str(&" ".repeat(column_spacing + dir_width));
145                }
146            }
147
148            // Add spacing after directories if executables or files exist
149            if exec_num_cols > 0 || file_num_cols > 0 {
150                line.push_str(&" ".repeat(column_spacing));
151            }
152        }
153
154        // Executables section
155        if exec_num_cols > 0 {
156            for col in 0..exec_num_cols {
157                let idx = col * max_rows + row;
158                if idx < executables.len() {
159                    if col > 0 {
160                        line.push_str(&" ".repeat(column_spacing));
161                    }
162
163                    let entry = &executables[idx];
164                    let filename = entry.path.to_string_lossy();
165                    let icon = entry.get_icon_custom(&config.icons);
166                    let actual_width = UnicodeWidthStr::width(icon.as_str())
167                        + 1
168                        + UnicodeWidthStr::width(filename.as_ref());
169
170                    line.push_str(&format!(
171                        "{} {}",
172                        icon.color(entry.get_icon_color(&config.icons.colors)),
173                        filename.color(entry.get_color(&config.colors)).bold()
174                    ));
175
176                    if actual_width < exec_width {
177                        line.push_str(&" ".repeat(exec_width - actual_width));
178                    }
179                    has_any_content = true;
180                } else if col == 0 {
181                    line.push_str(&" ".repeat(exec_width));
182                } else {
183                    line.push_str(&" ".repeat(column_spacing + exec_width));
184                }
185            }
186
187            // Add spacing after executables if files exist
188            if file_num_cols > 0 {
189                line.push_str(&" ".repeat(column_spacing));
190            }
191        }
192
193        // Regular files section
194        if file_num_cols > 0 {
195            for col in 0..file_num_cols {
196                let idx = col * max_rows + row;
197                if idx < regular_files.len() {
198                    if col > 0 {
199                        line.push_str(&" ".repeat(column_spacing));
200                    }
201
202                    let entry = &regular_files[idx];
203                    let filename = entry.path.to_string_lossy();
204                    let icon = entry.get_icon_custom(&config.icons);
205                    let actual_width = UnicodeWidthStr::width(icon.as_str())
206                        + 1
207                        + UnicodeWidthStr::width(filename.as_ref());
208
209                    line.push_str(&format!(
210                        "{} {}",
211                        icon.color(entry.get_icon_color(&config.icons.colors)),
212                        filename.color(entry.get_color(&config.colors))
213                    ));
214
215                    if col < file_num_cols - 1 && actual_width < file_width {
216                        line.push_str(&" ".repeat(file_width - actual_width));
217                    }
218                    has_any_content = true;
219                }
220            }
221        }
222
223        if has_any_content {
224            println!("{}", line.trim_end());
225        }
226    }
227}
228
229fn format_single_column_per_type(
230    directories: Vec<FileEntry>,
231    executables: Vec<FileEntry>,
232    regular_files: Vec<FileEntry>,
233    column_spacing: usize,
234    config: &Config,
235) {
236    // Calculate column widths (icon + space + filename)
237    let dir_width = directories
238        .iter()
239        .map(|e| {
240            let filename = e.path.to_string_lossy();
241            let icon = e.get_icon_custom(&config.icons);
242            UnicodeWidthStr::width(icon.as_str()) + 1 + UnicodeWidthStr::width(filename.as_ref())
243        })
244        .max()
245        .unwrap_or(0);
246
247    let exec_width = executables
248        .iter()
249        .map(|e| {
250            let filename = e.path.to_string_lossy();
251            let icon = e.get_icon_custom(&config.icons);
252            UnicodeWidthStr::width(icon.as_str()) + 1 + UnicodeWidthStr::width(filename.as_ref())
253        })
254        .max()
255        .unwrap_or(0);
256
257    let file_width = regular_files
258        .iter()
259        .map(|e| {
260            let filename = e.path.to_string_lossy();
261            let icon = e.get_icon_custom(&config.icons);
262            UnicodeWidthStr::width(icon.as_str()) + 1 + UnicodeWidthStr::width(filename.as_ref())
263        })
264        .max()
265        .unwrap_or(0);
266
267    // Determine how many rows we need
268    let max_rows = *[directories.len(), executables.len(), regular_files.len()]
269        .iter()
270        .max()
271        .unwrap_or(&0);
272
273    // Print side-by-side columns
274    for i in 0..max_rows {
275        let mut line = String::new();
276
277        // Directory column
278        if dir_width > 0 {
279            if i < directories.len() {
280                let entry = &directories[i];
281                let filename = entry.path.to_string_lossy();
282                let icon = entry.get_icon_custom(&config.icons);
283                let actual_width = UnicodeWidthStr::width(icon.as_str())
284                    + 1
285                    + UnicodeWidthStr::width(filename.as_ref());
286
287                line.push_str(&format!(
288                    "{} {}",
289                    icon.color(entry.get_icon_color(&config.icons.colors)),
290                    filename.color(entry.get_color(&config.colors)).bold()
291                ));
292                // Add padding after the colored text
293                if actual_width < dir_width {
294                    line.push_str(&" ".repeat(dir_width - actual_width));
295                }
296            } else {
297                // Empty space for this row in directory column
298                line.push_str(&" ".repeat(dir_width));
299            }
300
301            // Add spacing after directory column if there are more columns
302            if exec_width > 0 || file_width > 0 {
303                line.push_str(&" ".repeat(column_spacing));
304            }
305        }
306
307        // Executable column
308        if exec_width > 0 {
309            if i < executables.len() {
310                let entry = &executables[i];
311                let filename = entry.path.to_string_lossy();
312                let icon = entry.get_icon_custom(&config.icons);
313                let actual_width = UnicodeWidthStr::width(icon.as_str())
314                    + 1
315                    + UnicodeWidthStr::width(filename.as_ref());
316
317                line.push_str(&format!(
318                    "{} {}",
319                    icon.color(entry.get_icon_color(&config.icons.colors)),
320                    filename.color(entry.get_color(&config.colors)).bold()
321                ));
322                // Add padding after the colored text
323                if actual_width < exec_width {
324                    line.push_str(&" ".repeat(exec_width - actual_width));
325                }
326            } else {
327                // Empty space for this row in executable column
328                line.push_str(&" ".repeat(exec_width));
329            }
330
331            // Add spacing after executable column if there are regular files
332            if file_width > 0 {
333                line.push_str(&" ".repeat(column_spacing));
334            }
335        }
336
337        // Regular files column
338        if i < regular_files.len() {
339            let entry = &regular_files[i];
340            let filename = entry.path.to_string_lossy();
341            let icon = entry.get_icon_custom(&config.icons);
342            let actual_width = UnicodeWidthStr::width(icon.as_str())
343                + 1
344                + UnicodeWidthStr::width(filename.as_ref());
345
346            line.push_str(&format!(
347                "{} {}",
348                icon.color(entry.get_icon_color(&config.icons.colors)),
349                filename.color(entry.get_color(&config.colors))
350            ));
351            // Add padding after the colored text
352            if actual_width < file_width {
353                line.push_str(&" ".repeat(file_width - actual_width));
354            }
355        }
356
357        println!("{}", line.trim_end());
358    }
359}