Skip to main content

plot/lib/display/
table_data.rs

1use colored::Colorize;
2
3use super::{Color, DrawTree, Icon};
4use crate::lib::logic::{FileKind, TabColumn, TabPathStructure, TableData, TableOutput, TableRow};
5
6fn format_size(bytes: u64) -> String {
7	if bytes < 1024 {
8		format!("{} B", bytes)
9	} else if bytes < 1024 * 1024 {
10		format!("{:.2} kB", bytes as f64 / 1024.0)
11	} else {
12		format!("{:.2} MB", bytes as f64 / (1024.0 * 1024.0))
13	}
14}
15
16impl std::fmt::Display for TableData {
17	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18		let total = self.rows.len();
19		if total == 0 {
20			return write!(f, "{} {} Pusta tabela", DrawTree::ITEM_LAST, Icon::EMPTY);
21		}
22
23		let mut lines = Vec::new();
24
25		for (i, row) in self.rows.iter().enumerate() {
26			let prefix = DrawTree::list(i, total);
27
28			let kind_icon = match row.kind {
29				FileKind::Dir => Icon::FOLDER,
30				FileKind::Binary => "๐Ÿ’พ",
31				FileKind::Text => Icon::FILE,
32				FileKind::Other => "โ“",
33			};
34
35			lines.push(format!(
36				"{} {} [{:>10}] | {} | {}",
37				prefix,
38				kind_icon,
39				format_size(row.size),
40				row.modified.format("%Y-%m-%d %H:%M"),
41				row.path
42			));
43		}
44
45		write!(f, "{}", lines.join("\n"))
46	}
47}
48
49impl std::fmt::Display for TableOutput {
50	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51		let total_rows = self.data.rows.len();
52
53		let (rows_to_show, index_offset) = if let Some(size) = self.trim_size {
54			let start = self.trim_page.saturating_sub(1) * size;
55			(self.data.rows.iter().skip(start).take(size).collect::<Vec<_>>(), start)
56		} else {
57			(self.data.rows.iter().collect::<Vec<_>>(), 0)
58		};
59
60		let current_view_count = rows_to_show.len();
61		let num_width = total_rows.to_string().len();
62
63		let mut prefix_map = std::collections::HashMap::new();
64
65		if self.data.structure == TabPathStructure::Tree {
66			use std::{
67				collections::BTreeMap,
68				path::{Path, PathBuf},
69			};
70
71			let clean_paths: Vec<PathBuf> =
72				self.data.rows.iter().map(|r| PathBuf::from(r.path.trim_end_matches('/'))).collect();
73
74			let mut tree_map: BTreeMap<PathBuf, Vec<&TableRow>> = BTreeMap::new();
75
76			for (i, p) in clean_paths.iter().enumerate() {
77				let parent = p.parent().map_or_else(|| PathBuf::from("."), Path::to_path_buf);
78				tree_map.entry(parent).or_default().push(&self.data.rows[i]);
79			}
80
81			let mut roots = Vec::new();
82			for (i, p) in clean_paths.iter().enumerate() {
83				let parent = p.parent().map_or_else(|| PathBuf::from("."), Path::to_path_buf);
84				let is_root = parent == Path::new(".") || parent == Path::new("") || !clean_paths.contains(&parent);
85				if is_root {
86					roots.push(&self.data.rows[i]);
87				}
88			}
89
90			fn build_prefixes(
91				nodes: &[&TableRow],
92				tree_map: &BTreeMap<PathBuf, Vec<&TableRow>>,
93				indent: &str,
94				out_map: &mut std::collections::HashMap<String, String>,
95			) {
96				for (i, node) in nodes.iter().enumerate() {
97					let clean_p = PathBuf::from(node.path.trim_end_matches('/'));
98					let is_last = i == nodes.len() - 1;
99					let children = tree_map.get(&clean_p);
100					let has_children = children.is_some() && !children.unwrap().is_empty();
101					let is_dir = node.kind == FileKind::Dir;
102
103					let (branch, next_indent) = DrawTree::tree(is_dir, is_last, has_children);
104
105					let prefix = format!("{}{}", indent, branch);
106					out_map.insert(node.path.clone(), prefix);
107
108					if let Some(child_nodes) = children {
109						let new_indent = format!("{}{}", indent, next_indent);
110						build_prefixes(child_nodes, tree_map, &new_indent, out_map);
111					}
112				}
113			}
114
115			build_prefixes(&roots, &tree_map, "", &mut prefix_map);
116		}
117
118		let mut output_lines = Vec::new();
119
120		for (i, row) in rows_to_show.iter().enumerate() {
121			let actual_idx = index_offset + i + 1;
122
123			let tree_list_prefix_str = if self.data.structure == TabPathStructure::Tree {
124				prefix_map.get(&row.path).map(|s| s.as_str()).unwrap_or("")
125			} else {
126				DrawTree::list(i, current_view_count)
127			};
128			let tree_list_prefix = Color::tree(tree_list_prefix_str);
129
130			let icon_str = if self.more_icons {
131				let is_dir = row.kind == FileKind::Dir;
132				let file_name = row.path.trim_end_matches('/').split('/').next_back().unwrap_or("");
133				let is_hidden = file_name.starts_with('.');
134
135				if is_dir {
136					if is_hidden { Icon::FOLDER2_HIDDEN } else { Icon::FOLDER2 }
137				} else if is_hidden {
138					Icon::FILE2_HIDDEN
139				} else {
140					let path_obj = std::path::Path::new(&row.path);
141					if let Some(ext) = path_obj.extension().and_then(|e| e.to_str()) {
142						match ext.to_lowercase().as_str() {
143							"rs" => Icon::LANG_RUST,
144							"toml" => "โš™๏ธ",
145							"slint" => "๐ŸŽจ",
146							"md" => "๐Ÿ“",
147							"json" => "๐Ÿ”ฃ",
148							"yaml" | "yml" => "๐Ÿ› ๏ธ",
149							"html" => "๐Ÿ“–",
150							"css" => "๐Ÿ–Œ๏ธ",
151							"js" => "๐Ÿ“œ",
152							"ts" => "๐Ÿ“˜",
153							_ => {
154								if row.kind == FileKind::Binary {
155									"๐Ÿ’พ"
156								} else {
157									Icon::FILE2
158								}
159							}
160						}
161					} else {
162						if row.kind == FileKind::Binary { "๐Ÿ’พ" } else { Icon::FILE2 }
163					}
164				}
165			} else {
166				match row.kind {
167					FileKind::Dir => Icon::FOLDER,
168					FileKind::Binary => "๐Ÿ’พ",
169					FileKind::Text => Icon::FILE,
170					FileKind::Other => "โ“",
171				}
172			};
173
174			let path_styled = match row.kind {
175				FileKind::Dir => Color::folder(&row.path),
176				FileKind::Binary => Color::binary(&row.path),
177				FileKind::Text => Color::file(&row.path),
178				FileKind::Other => row.path.dimmed(),
179			};
180
181			let icon = match row.kind {
182				FileKind::Dir => icon_str.yellow(),
183				FileKind::Binary => icon_str.magenta(),
184				FileKind::Text => icon_str.normal(),
185				FileKind::Other => icon_str.red(),
186			};
187
188			let size_str = Color::size(&format!("{:>10}", format_size(row.size)));
189			let pos_num = Color::num(&format!("{:>width$}.", actual_idx, width = num_width));
190			let date_str = Color::date(&row.modified.format("%Y W%V %a").to_string());
191			let time_str = Color::time(&row.modified.format("%H:%M:%S.%3f").to_string());
192
193			let mut line_parts = Vec::new();
194			for (col_idx, col) in self.columns.iter().enumerate() {
195				let part = match col {
196					TabColumn::TreeList => tree_list_prefix.to_string(),
197					TabColumn::Number => pos_num.to_string(),
198					TabColumn::Icon => icon.to_string(),
199					TabColumn::Size => {
200						format!("{}{}{}", Color::border("[ "), size_str, Color::border(" ]"))
201					}
202					TabColumn::Date => {
203						let pipe = Color::border("|");
204						let next_is_time = matches!(self.columns.get(col_idx + 1), Some(TabColumn::Time));
205						let prev_is_time = col_idx > 0 && self.columns[col_idx - 1] == TabColumn::Time;
206						match (prev_is_time, next_is_time) {
207							(false, true) => format!("{} {}", pipe, date_str),
208							(true, false) => format!("{} {}", date_str, pipe),
209							(true, true) => format!("{}", date_str),
210							(false, false) => format!("{} {} {}", pipe, date_str, pipe),
211						}
212					}
213					TabColumn::Time => {
214						let pipe = Color::border("|");
215						let next_is_date = matches!(self.columns.get(col_idx + 1), Some(TabColumn::Date));
216						let prev_is_date = col_idx > 0 && self.columns[col_idx - 1] == TabColumn::Date;
217						match (prev_is_date, next_is_date) {
218							(false, true) => format!("{} {}", pipe, time_str),
219							(true, false) => format!("{} {}", time_str, pipe),
220							(true, true) => format!("{}", time_str),
221							(false, false) => format!("{} {} {}", pipe, time_str, pipe),
222						}
223					}
224					TabColumn::Path => path_styled.to_string(),
225				};
226				line_parts.push(part);
227			}
228
229			output_lines.push(line_parts.join(" "));
230		}
231
232		if let Some(size) = self.trim_size {
233			let total_pages = total_rows.saturating_add(size - 1) / size;
234			if total_pages > 1 {
235				output_lines.push(format!(
236					"          {}",
237					format!("... Strona {} z {} (ล‚ฤ…cznie {} pozycji)", self.trim_page, total_pages, total_rows)
238						.italic()
239						.dimmed()
240				));
241			} else if total_rows > size {
242				output_lines.push(format!(
243					"          {}",
244					format!("... i {} innych pozycji", total_rows - size).italic().dimmed()
245				));
246			}
247		}
248
249		write!(f, "{}", output_lines.join("\n"))
250	}
251}