1use crate::config::Config;
2use crate::file_entry::FileEntry;
3use crate::formatter::long::{calculate_column_widths, print_long_entries_with_widths};
4use crate::sort::sort_default;
5use colored::Colorize;
6use std::fs;
7use std::os::unix::fs::PermissionsExt;
8use std::path::Path;
9
10pub fn format_recursive(path: &Path, config: &Config, show_hidden: bool, use_long_format: bool) {
11 let recursive_long_style = &config.display.tree.recursive_long_format;
12 if let Some(dir_name) = path.file_name() {
14 let dir_name_str = dir_name.to_string_lossy();
15
16 let temp_entry = FileEntry {
18 path: dir_name.to_os_string(),
19 is_dir: true,
20 is_executable: false,
21 mode: 0o755,
22 size: 0,
23 modified: std::time::SystemTime::UNIX_EPOCH,
24 owner: String::new(),
25 group: String::new(),
26 nlink: 0,
27 };
28
29 let icon = temp_entry.get_icon_custom(&config.icons);
30 let icon_colored = icon.color(temp_entry.get_icon_color(&config.icons.colors));
31 let dir_name_colored = dir_name_str
32 .color(temp_entry.get_color(&config.colors))
33 .bold();
34
35 if icon.is_empty() {
37 println!("{}", dir_name_colored);
38 } else {
39 println!("{} {}", icon_colored, dir_name_colored);
40 }
41 }
42
43 print_directory_tree(
45 path,
46 config,
47 show_hidden,
48 "",
49 &config.display.tree.style,
50 use_long_format,
51 recursive_long_style.as_str(),
52 );
53}
54
55fn print_directory_tree(
56 path: &Path,
57 config: &Config,
58 show_hidden: bool,
59 prefix: &str,
60 tree_style: &str,
61 use_long_format: bool,
62 recursive_long_style: &str,
63) {
64 match fs::read_dir(path) {
65 Ok(entries_iter) => {
66 let mut entries: Vec<_> = entries_iter.filter_map(|e| e.ok()).collect();
67
68 entries.sort_by(|a, b| {
70 let a_name = a.file_name();
71 let b_name = b.file_name();
72 a_name.cmp(&b_name)
73 });
74
75 let entries: Vec<_> = entries
77 .into_iter()
78 .filter(|e| {
79 if show_hidden {
80 true
81 } else {
82 let file_name = e.file_name();
83 let name_str = file_name.to_string_lossy();
84 !name_str.starts_with('.')
85 }
86 })
87 .collect();
88
89 if use_long_format {
90 if recursive_long_style == "header" {
91 print_long_format_with_headers(
93 &entries,
94 path,
95 config,
96 prefix,
97 show_hidden,
98 tree_style,
99 recursive_long_style,
100 );
101 } else {
102 let mut file_entries: Vec<(FileEntry, std::path::PathBuf)> = Vec::new();
105
106 for entry in entries.iter() {
107 let entry_path = entry.path();
108 let file_name = entry.file_name();
109 if let Ok(metadata) = entry.metadata() {
110 let is_dir = metadata.is_dir();
111 let is_executable =
112 !is_dir && (metadata.permissions().mode() & 0o111) != 0;
113
114 let file_entry = FileEntry {
115 path: file_name.clone(),
116 is_dir,
117 is_executable,
118 mode: metadata.permissions().mode(),
119 size: metadata.len(),
120 modified: metadata
121 .modified()
122 .unwrap_or(std::time::SystemTime::UNIX_EPOCH),
123 owner: String::new(),
124 group: String::new(),
125 nlink: 0,
126 };
127
128 file_entries.push((file_entry, entry_path));
129 }
130 }
131
132 let mut entries_only: Vec<FileEntry> =
134 file_entries.iter().map(|(e, _)| e.clone()).collect();
135 sort_default(&mut entries_only);
137 let fields = &config.display.long_format_fields;
138 let widths = calculate_column_widths(&entries_only, fields);
139
140 for entry in &entries_only {
142 let entry_path = file_entries
144 .iter()
145 .find(|(e, _)| e.path.to_string_lossy() == entry.path.to_string_lossy())
146 .map(|(_, p)| p.clone())
147 .unwrap_or_else(|| std::path::PathBuf::new());
148 let single_entry = vec![entry.clone()];
150 print_long_entries_with_widths(
151 &single_entry,
152 config,
153 prefix,
154 fields,
155 &widths,
156 );
157
158 if entry.is_dir {
160 let new_prefix = format!("{} ", prefix);
161 print_directory_tree(
162 &entry_path,
163 config,
164 show_hidden,
165 &new_prefix,
166 tree_style,
167 use_long_format,
168 recursive_long_style,
169 );
170 }
171 }
172 }
173 } else {
174 for (idx, entry) in entries.iter().enumerate() {
176 let is_last = idx == entries.len() - 1;
177 let entry_path = entry.path();
178 let file_name = entry.file_name();
179 let file_name_str = file_name.to_string_lossy();
180
181 if let Ok(metadata) = entry.metadata() {
183 let is_dir = metadata.is_dir();
184 let is_executable = !is_dir && (metadata.permissions().mode() & 0o111) != 0;
185
186 let file_entry = FileEntry {
188 path: file_name.clone(),
189 is_dir,
190 is_executable,
191 mode: metadata.permissions().mode(),
192 size: metadata.len(),
193 modified: metadata
194 .modified()
195 .unwrap_or(std::time::SystemTime::UNIX_EPOCH),
196 owner: String::new(),
197 group: String::new(),
198 nlink: 0,
199 };
200
201 let (connector, extension_prefix) = if tree_style == "ascii" {
203 if is_last {
204 ("└──", " ")
205 } else {
206 ("├──", "│ ")
207 }
208 } else {
209 ("", " ")
211 };
212
213 let icon = file_entry.get_icon_custom(&config.icons);
215 let icon_colored =
216 icon.color(file_entry.get_icon_color(&config.icons.colors));
217
218 let filename_colored = match file_entry.get_file_type() {
220 crate::file_entry::FileType::Directory
221 | crate::file_entry::FileType::Executable => file_name_str
222 .color(file_entry.get_color(&config.colors))
223 .bold(),
224 crate::file_entry::FileType::RegularFile => {
225 file_name_str.color(file_entry.get_color(&config.colors))
226 }
227 };
228
229 println!(
231 "{}{}{} {}",
232 prefix, connector, icon_colored, filename_colored
233 );
234
235 if is_dir {
237 let new_prefix = if tree_style == "ascii" {
238 format!("{}{}", prefix, extension_prefix)
239 } else {
240 format!("{} ", prefix)
241 };
242
243 print_directory_tree(
244 &entry_path,
245 config,
246 show_hidden,
247 &new_prefix,
248 tree_style,
249 use_long_format,
250 recursive_long_style,
251 );
252 }
253 }
254 }
255 }
256 }
257 Err(_) => {}
258 }
259}
260
261fn print_long_format_with_headers(
262 entries: &[std::fs::DirEntry],
263 path: &std::path::Path,
264 config: &crate::config::Config,
265 prefix: &str,
266 show_hidden: bool,
267 tree_style: &str,
268 recursive_long_style: &str,
269) {
270 if !prefix.is_empty() {
272 println!("{}:", path.display());
273 }
274
275 let mut file_entries: Vec<(FileEntry, std::path::PathBuf)> = Vec::new();
277
278 for entry in entries.iter() {
279 let entry_path = entry.path();
280 let file_name = entry.file_name();
281 if let Ok(metadata) = entry.metadata() {
282 let is_dir = metadata.is_dir();
283 let is_executable = !is_dir && (metadata.permissions().mode() & 0o111) != 0;
284
285 let file_entry = FileEntry {
286 path: file_name.clone(),
287 is_dir,
288 is_executable,
289 mode: metadata.permissions().mode(),
290 size: metadata.len(),
291 modified: metadata
292 .modified()
293 .unwrap_or(std::time::SystemTime::UNIX_EPOCH),
294 owner: String::new(),
295 group: String::new(),
296 nlink: 0,
297 };
298
299 file_entries.push((file_entry, entry_path));
300 }
301 }
302
303 let mut file_entries_only: Vec<FileEntry> =
305 file_entries.iter().map(|(e, _)| e.clone()).collect();
306 if !file_entries_only.is_empty() {
307 sort_default(&mut file_entries_only);
309 let fields = &config.display.long_format_fields;
310 let widths = calculate_column_widths(&file_entries_only, fields);
311 print_long_entries_with_widths(&file_entries_only, config, "", fields, &widths);
312 }
313
314 for (entry, entry_path) in &file_entries {
316 if entry.is_dir {
317 let new_prefix = format!("{} ", prefix);
318 print_directory_tree(
319 &entry_path,
320 config,
321 show_hidden,
322 &new_prefix,
323 tree_style,
324 true,
325 recursive_long_style,
326 );
327 }
328 }
329}