lx_cli/formatter/
long.rs

1use crate::config::Config;
2use crate::file_entry::{FileEntry, FileType};
3use crate::sort::sort_default;
4use colored::Colorize;
5
6pub fn format_long(mut entries: Vec<FileEntry>, config: &Config) {
7    // Apply default sorting: by type, then alphabetically (case-insensitive)
8    sort_default(&mut entries);
9
10    print_long_entries(&entries, config, "");
11}
12
13pub fn print_long_entries(entries: &[FileEntry], config: &Config, prefix: &str) {
14    if entries.is_empty() {
15        return;
16    }
17
18    let fields = &config.display.long_format_fields;
19    let widths = calculate_column_widths(entries, fields);
20    print_long_entries_with_widths(entries, config, prefix, fields, &widths);
21}
22
23pub fn calculate_column_widths(
24    entries: &[FileEntry],
25    fields: &[String],
26) -> std::collections::HashMap<String, usize> {
27    let mut max_widths: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
28
29    for field in fields {
30        let width = match field.as_str() {
31            "nlink" => entries
32                .iter()
33                .map(|e| e.nlink.to_string().len())
34                .max()
35                .unwrap_or(0),
36            "owner" => entries.iter().map(|e| e.owner.len()).max().unwrap_or(0),
37            "group" => entries.iter().map(|e| e.group.len()).max().unwrap_or(0),
38            "size" => entries
39                .iter()
40                .map(|e| e.format_size().len())
41                .max()
42                .unwrap_or(0),
43            "filename" => entries
44                .iter()
45                .map(|e| e.path.to_string_lossy().len())
46                .max()
47                .unwrap_or(0),
48            "permissions" => entries
49                .iter()
50                .map(|e| e.format_permissions().len())
51                .max()
52                .unwrap_or(0),
53            _ => 0,
54        };
55        max_widths.insert(field.clone(), width);
56    }
57
58    max_widths
59}
60
61pub fn print_long_entries_with_widths(
62    entries: &[FileEntry],
63    config: &Config,
64    prefix: &str,
65    fields: &[String],
66    widths: &std::collections::HashMap<String, usize>,
67) {
68    // Print each entry
69    for entry in entries {
70        let mut output_parts: Vec<String> = Vec::new();
71
72        for (idx, field) in fields.iter().enumerate() {
73            let part = match field.as_str() {
74                "permissions" => entry.format_permissions(),
75                "nlink" => {
76                    let width = widths.get("nlink").copied().unwrap_or(0);
77                    format!("{:>width$}", entry.nlink.to_string(), width = width)
78                }
79                "owner" => {
80                    let width = widths.get("owner").copied().unwrap_or(0);
81                    format!("{:<width$}", entry.owner, width = width)
82                }
83                "group" => {
84                    let width = widths.get("group").copied().unwrap_or(0);
85                    format!("{:<width$}", entry.group, width = width)
86                }
87                "size" => {
88                    let width = widths.get("size").copied().unwrap_or(0);
89                    format!("{:>width$}", entry.format_size(), width = width)
90                }
91                "modified" => entry.format_modified(),
92                "icon" => {
93                    let icon = entry.get_icon_custom(&config.icons);
94                    let icon_color = entry.get_icon_color(&config.icons.colors);
95                    format!("{}", icon.color(icon_color))
96                }
97                "filename" => {
98                    let filename_str = entry.path.to_string_lossy().to_string();
99                    let width = widths.get("filename").copied().unwrap_or(0);
100
101                    // Pad filename before applying color
102                    let padded = if idx < fields.len() - 1 {
103                        format!("{:<width$}", filename_str, width = width)
104                    } else {
105                        filename_str
106                    };
107
108                    let filename_colored = match entry.get_file_type() {
109                        FileType::Directory | FileType::Executable => {
110                            padded.color(entry.get_color(&config.colors)).bold()
111                        }
112                        FileType::RegularFile => padded.color(entry.get_color(&config.colors)),
113                    };
114                    format!("{}", filename_colored)
115                }
116                _ => String::new(),
117            };
118            output_parts.push(part);
119        }
120
121        println!("{}{}", prefix, output_parts.join("  "));
122    }
123}