lx_cli/formatter/
short.rs

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