1use crate::colors::{bold_hex, hex_color};
10use crate::config::Config;
11use crate::duplicates::{DuplicateGroup, DuplicateSummary};
12use crate::searcher::SearchMatch;
13use colored::Colorize;
14use std::path::Path;
15use std::time::Duration;
16
17pub struct Printer<'a> {
18 cfg: &'a Config,
19}
20
21impl<'a> Printer<'a> {
22 pub fn new(cfg: &'a Config) -> Self {
23 Self { cfg }
24 }
25
26 pub fn print_banner(&self) {
29 eprintln!(
30 "⚡ {} v{}",
31 bold_hex("fsearch", &self.cfg.color_info),
32 bold_hex(env!("CARGO_PKG_VERSION"), &self.cfg.color_count),
33 );
34 }
35
36 pub fn print_searching(&self, dirs: &[&Path], pattern: &str) {
37 let dir_list = dirs
38 .iter()
39 .map(|d| {
40 format!(
41 "{}",
42 bold_hex(d.display().to_string(), &self.cfg.color_path)
43 )
44 })
45 .collect::<Vec<_>>()
46 .join(hex_color(", ", "#888888").to_string().as_str());
47 eprintln!(
48 "🔍 Searching [{}] for {}",
49 dir_list,
50 bold_hex(pattern, &self.cfg.color_pattern),
51 );
52 }
53
54 pub fn print_scanning_dups(&self, dirs: &[&Path], mode: &str, algo: &str) {
55 let dir_list = dirs
56 .iter()
57 .map(|d| {
58 format!(
59 "{}",
60 bold_hex(d.display().to_string(), &self.cfg.color_path)
61 )
62 })
63 .collect::<Vec<_>>()
64 .join(hex_color(", ", "#888888").to_string().as_str());
65 eprintln!(
66 "🔎 Scanning [{}] [mode: {} algo: {}]",
67 dir_list,
68 bold_hex(mode, &self.cfg.color_pattern),
69 bold_hex(algo, &self.cfg.color_info),
70 );
71 }
72
73 pub fn print_results(
76 &self,
77 results: &[SearchMatch],
78 _search_in_files: bool,
79 elapsed: Duration,
80 ) {
81 if results.is_empty() {
82 println!(
83 "\n🕳️ {}\n",
84 bold_hex("No results found.", &self.cfg.color_warn)
85 );
86 return;
87 }
88
89 let count = results.len().to_string();
90 let ms = elapsed.as_millis();
91
92 println!(
93 "\n{} {} {} {} {}",
94 "📋".bold(),
95 bold_hex("FOUND:", &self.cfg.color_header),
96 bold_hex(&count, &self.cfg.color_count),
97 hex_color("result(s)", &self.cfg.color_info),
98 hex_color(format!("[{ms}ms]"), "#888888"),
99 );
100 println!();
101
102 let zfill = count.len();
103 for (idx, item) in results.iter().enumerate() {
104 let num = bold_hex(
105 format!("{:0>w$}", idx + 1, w = zfill),
106 &self.cfg.color_index,
107 );
108 match item {
109 SearchMatch::Path(path) => {
110 println!(
111 "{}. {}",
112 num,
113 bold_hex(path.display().to_string(), &self.cfg.color_path)
114 );
115 }
116 SearchMatch::Content { path, lines } => {
117 println!(
118 "{}. {}",
119 num,
120 bold_hex(path.display().to_string(), &self.cfg.color_path)
121 );
122 for (line_num, line_text) in lines {
123 println!(
124 " {} {} {}",
125 bold_hex(format!("{:>5}", line_num), &self.cfg.color_line_num),
126 hex_color("│", "#555555"),
127 hex_color(line_text.trim_end(), &self.cfg.color_line_text),
128 );
129 }
130 }
131 }
132 }
133 println!();
134 }
135
136 pub fn print_duplicates(
139 &self,
140 groups: &[DuplicateGroup],
141 summary: &DuplicateSummary,
142 elapsed: Duration,
143 ) {
144 let ms = elapsed.as_millis();
145
146 if groups.is_empty() {
147 println!(
148 "\n✅ {} {}\n",
149 bold_hex("No duplicates found.", &self.cfg.color_info),
150 hex_color(format!("[{ms}ms]"), "#888888"),
151 );
152 self.print_dup_summary(summary);
153 return;
154 }
155
156 println!(
157 "\n{} {} {} {} {}",
158 "🔁".bold(),
159 bold_hex("DUPLICATE GROUPS:", &self.cfg.color_header),
160 bold_hex(groups.len().to_string(), &self.cfg.color_count),
161 hex_color("group(s)", &self.cfg.color_info),
162 hex_color(format!("[{ms}ms]"), "#888888"),
163 );
164 println!();
165
166 let zfill = groups.len().to_string().len();
167 for (idx, group) in groups.iter().enumerate() {
168 let num = bold_hex(
169 format!("{:0>w$}", idx + 1, w = zfill),
170 &self.cfg.color_dup_group,
171 );
172 let hash_preview = &group.hash[..group.hash.len().min(16)];
174 println!(
175 "{}. {} {} {} {} {} {}",
176 num,
177 hex_color("size:", "#888888"),
178 bold_hex(group.size_human(), &self.cfg.color_dup_size),
179 hex_color("wasted:", "#888888"),
180 bold_hex(group.wasted_human(), &self.cfg.color_warn),
181 hex_color("hash:", "#888888"),
182 hex_color(hash_preview, "#666666"),
183 );
184
185 for (pi, path) in group.paths.iter().enumerate() {
187 let marker = if pi == 0 { " ◉" } else { " ○" };
188 println!(
189 "{} {}",
190 hex_color(marker, &self.cfg.color_dup_group),
191 hex_color(path.display().to_string(), &self.cfg.color_dup_path),
192 );
193 }
194 println!();
195 }
196
197 self.print_dup_summary(summary);
198 }
199
200 fn print_dup_summary(&self, s: &DuplicateSummary) {
201 println!(
202 "{} scanned {} file(s) in {} dir(s) · {} group(s) · {} duplicate(s) · {} wasted",
203 bold_hex("📊 Summary:", &self.cfg.color_header),
204 bold_hex(s.files_scanned.to_string(), &self.cfg.color_count),
205 bold_hex(s.dirs_scanned.to_string(), &self.cfg.color_count),
206 bold_hex(s.groups_found.to_string(), &self.cfg.color_count),
207 bold_hex(s.duplicate_files.to_string(), &self.cfg.color_count),
208 bold_hex(s.wasted_human(), &self.cfg.color_warn),
209 );
210 println!();
211 }
212
213 pub fn print_error(&self, msg: &str) {
216 eprintln!("❌ {} {}", bold_hex("Error:", &self.cfg.color_error), msg);
217 }
218
219 pub fn print_warn(&self, msg: &str) {
220 eprintln!("⚠️ {}", hex_color(msg, &self.cfg.color_warn));
221 }
222
223 pub fn print_info(&self, msg: &str) {
224 eprintln!("ℹ️ {}", hex_color(msg, &self.cfg.color_info));
225 }
226}