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