lx_cli/formatter/
recursive.rs

1use crate::config::Config;
2use crate::file_entry::FileEntry;
3use colored::Colorize;
4use std::fs;
5use std::os::unix::fs::PermissionsExt;
6use std::path::Path;
7
8pub fn format_recursive(path: &Path, config: &Config, show_hidden: bool) {
9    // Print the root directory as the parent
10    if let Some(dir_name) = path.file_name() {
11        let dir_name_str = dir_name.to_string_lossy();
12
13        // Create a temporary FileEntry just for getting the directory icon
14        let temp_entry = FileEntry {
15            path: dir_name.to_os_string(),
16            is_dir: true,
17            is_executable: false,
18            mode: 0o755,
19            size: 0,
20            modified: std::time::SystemTime::UNIX_EPOCH,
21            owner: String::new(),
22            group: String::new(),
23            nlink: 0,
24        };
25
26        let icon = temp_entry.get_icon_custom(&config.icons);
27        let icon_colored = icon.color(temp_entry.get_icon_color(&config.icons.colors));
28        let dir_name_colored = dir_name_str
29            .color(temp_entry.get_color(&config.colors))
30            .bold();
31
32        // Only add space if icon is not empty
33        if icon.is_empty() {
34            println!("{}", dir_name_colored);
35        } else {
36            println!("{} {}", icon_colored, dir_name_colored);
37        }
38    }
39
40    // Print the tree contents
41    print_directory_tree(path, config, show_hidden, "", &config.display.tree.style);
42}
43
44fn print_directory_tree(
45    path: &Path,
46    config: &Config,
47    show_hidden: bool,
48    prefix: &str,
49    tree_style: &str,
50) {
51    match fs::read_dir(path) {
52        Ok(entries_iter) => {
53            let mut entries: Vec<_> = entries_iter.filter_map(|e| e.ok()).collect();
54
55            // Sort entries by filename
56            entries.sort_by(|a, b| {
57                let a_name = a.file_name();
58                let b_name = b.file_name();
59                a_name.cmp(&b_name)
60            });
61
62            // Filter out hidden files if needed
63            let entries: Vec<_> = entries
64                .into_iter()
65                .filter(|e| {
66                    if show_hidden {
67                        true
68                    } else {
69                        let file_name = e.file_name();
70                        let name_str = file_name.to_string_lossy();
71                        !name_str.starts_with('.')
72                    }
73                })
74                .collect();
75
76            for (idx, entry) in entries.iter().enumerate() {
77                let is_last = idx == entries.len() - 1;
78                let entry_path = entry.path();
79                let file_name = entry.file_name();
80                let file_name_str = file_name.to_string_lossy();
81
82                // Get metadata
83                if let Ok(metadata) = entry.metadata() {
84                    let is_dir = metadata.is_dir();
85                    let is_executable = !is_dir && (metadata.permissions().mode() & 0o111) != 0;
86
87                    // Create FileEntry for icon/color handling
88                    let file_entry = FileEntry {
89                        path: file_name.clone(),
90                        is_dir,
91                        is_executable,
92                        mode: metadata.permissions().mode(),
93                        size: metadata.len(),
94                        modified: metadata
95                            .modified()
96                            .unwrap_or(std::time::SystemTime::UNIX_EPOCH),
97                        owner: String::new(),
98                        group: String::new(),
99                        nlink: 0,
100                    };
101
102                    // Determine tree connectors
103                    let (connector, extension_prefix) = if tree_style == "ascii" {
104                        if is_last {
105                            ("└──", "    ")
106                        } else {
107                            ("├──", "│   ")
108                        }
109                    } else {
110                        // indent style
111                        ("", "  ")
112                    };
113
114                    // Get icon and color
115                    let icon = file_entry.get_icon_custom(&config.icons);
116                    let icon_colored = icon.color(file_entry.get_icon_color(&config.icons.colors));
117
118                    // Get filename color
119                    let filename_colored = match file_entry.get_file_type() {
120                        crate::file_entry::FileType::Directory
121                        | crate::file_entry::FileType::Executable => file_name_str
122                            .color(file_entry.get_color(&config.colors))
123                            .bold(),
124                        crate::file_entry::FileType::RegularFile => {
125                            file_name_str.color(file_entry.get_color(&config.colors))
126                        }
127                    };
128
129                    // Print the entry
130                    println!(
131                        "{}{}{} {}",
132                        prefix, connector, icon_colored, filename_colored
133                    );
134
135                    // If it's a directory, recurse
136                    if is_dir {
137                        let new_prefix = if tree_style == "ascii" {
138                            format!("{}{}", prefix, extension_prefix)
139                        } else {
140                            format!("{}  ", prefix)
141                        };
142
143                        print_directory_tree(
144                            &entry_path,
145                            config,
146                            show_hidden,
147                            &new_prefix,
148                            tree_style,
149                        );
150                    }
151                }
152            }
153        }
154        Err(_) => {}
155    }
156}