1use super::CodeBlock;
2use super::{Classic, Renderable, Res};
3use crate::data::*;
4use crate::report::code::Point;
5use crate::report::code::{count_width, group_markers, LineGuide, Spaces};
6use crate::report::group_marker_lines;
7use crate::RenderConfig;
8
9use pathdiff::diff_paths;
10use std::fmt::Write;
11use std::path::PathBuf;
12use yansi::Paint;
13
14fn colorize_code(
15 markers: &mut [&(Point, Point, &Marker)],
16 code_line: &str,
17 modify: &dyn Fn(&str) -> String,
18 fmt: &mut dyn Write,
19) -> std::fmt::Result {
20 markers.sort_by(|x, y| x.0.column.cmp(&y.0.column));
21 let mut start = 0;
22
23 for marker in markers {
24 if start < marker.0.column {
25 write!(fmt, "{}", modify(&code_line[start..marker.0.column]))?;
26 start = marker.0.column;
27 }
28
29 let end = if marker.0.line == marker.1.line {
30 marker.1.column
31 } else {
32 code_line.len()
33 };
34
35 if start < end {
36 let colorizer = &marker.2.color.colorizer();
37 write!(fmt, "{}", colorizer(&code_line[start..end]).bold())?;
38 start = end;
39 }
40 }
41
42 if start < code_line.len() {
43 write!(fmt, "{}", modify(&code_line[start..code_line.len()]))?;
44 }
45
46 writeln!(fmt)?;
47 Ok(())
48}
49
50fn mark_inlined(
51 prefix: &str,
52 code: &str,
53 config: &RenderConfig,
54 inline_markers: &mut [&(Point, Point, &Marker)],
55 fmt: &mut dyn Write,
56) -> std::fmt::Result {
57 inline_markers.sort_by(|x, y| x.0.column.cmp(&y.0.column));
58 let mut start = 0;
59
60 write!(
61 fmt,
62 "{:>5} {} {}",
63 "",
64 paint_line(config.chars.vbar),
65 prefix
66 )?;
67
68 for marker in inline_markers.iter_mut() {
69 if start < marker.0.column {
70 let Spaces { width, tabs } = count_width(&code[start..marker.0.column]);
71 write!(fmt, "{:pad$}{}", "", "\t".repeat(tabs), pad = width)?;
72 start = marker.0.column;
73 }
74 if start < marker.1.column {
75 let Spaces { width, tabs } = count_width(&code[start..marker.1.column]);
76 let colorizer = marker.2.color.colorizer();
77 write!(fmt, "{}", colorizer(config.chars.bxline.to_string()))?;
78 write!(
79 fmt,
80 "{}",
81 colorizer(
82 config
83 .chars
84 .hbar
85 .to_string()
86 .repeat((width + tabs).saturating_sub(1))
87 )
88 )?;
89 start = marker.1.column;
90 }
91 }
92 writeln!(fmt)?;
93
94 for i in 0..inline_markers.len() {
96 write!(
97 fmt,
98 "{:>5} {} {}",
99 "",
100 paint_line(config.chars.vbar),
101 prefix
102 )?;
103 let mut start = 0;
104 for j in 0..(inline_markers.len() - i) {
105 let marker = inline_markers[j];
106 if start < marker.0.column {
107 let Spaces { width, tabs } = count_width(&code[start..marker.0.column]);
108 write!(fmt, "{:pad$}{}", "", "\t".repeat(tabs), pad = width)?;
109 start = marker.0.column;
110 }
111 if start < marker.1.column {
112 let colorizer = marker.2.color.colorizer();
113 if j == (inline_markers.len() - i).saturating_sub(1) {
114 write!(
115 fmt,
116 "{}",
117 colorizer(format!("{}{}", config.chars.trline, marker.2.text))
118 )?;
119 } else {
120 write!(fmt, "{}", colorizer(config.chars.vbar.to_string()))?;
121 }
122 start += 1;
123 }
124 }
125 writeln!(fmt)?;
126 }
127 Ok(())
128}
129
130fn paint_line<T>(data: T) -> Paint<T> {
131 Paint::new(data).fg(yansi::Color::Cyan).dimmed()
132}
133
134impl Color {
135 fn colorizer<T>(&self) -> &dyn Fn(T) -> Paint<T> {
136 match self {
137 Color::Fst => &|str| yansi::Paint::red(str).bold(),
138 Color::Snd => &|str| yansi::Paint::blue(str).bold(),
139 Color::Thr => &|str| yansi::Paint::green(str).bold(),
140 Color::For => &|str| yansi::Paint::yellow(str).bold(),
141 Color::Fft => &|str| yansi::Paint::cyan(str).bold(),
142 }
143 }
144
145 fn colorize<T>(&self, data: T) -> Paint<T> {
146 (self.colorizer())(data)
147 }
148}
149
150impl Renderable<Classic> for Severity {
151 fn render(&self, fmt: &mut dyn Write, _: &dyn FileCache, _: &RenderConfig) -> Res {
152 use Severity::*;
153
154 let painted = match self {
155 Error => Paint::new(" ERROR ").bg(yansi::Color::Red).bold(),
156 Warning => Paint::new(" WARN ").bg(yansi::Color::Yellow).bold(),
157 Info => Paint::new(" INFO ").bg(yansi::Color::Blue).bold(),
158 };
159
160 write!(fmt, " {} ", painted)
161 }
162}
163
164impl<'a> Renderable<Classic> for Header<'a> {
165 fn render(&self, fmt: &mut dyn Write, cache: &dyn FileCache, config: &RenderConfig) -> Res {
166 Renderable::<Classic>::render(self.severity, fmt, cache, config)?;
167 fmt.write_str(&Paint::new(&self.title).bold().to_string())?;
168 fmt.write_char('\n')
169 }
170}
171
172impl Renderable<Classic> for Subtitle {
173 fn render(&self, fmt: &mut dyn Write, cache: &dyn FileCache, config: &RenderConfig) -> Res {
174 match self {
175 Subtitle::Normal(color, phr) | Subtitle::Field(color, phr) => {
176 let bullet = color.colorize(config.chars.bullet);
177 writeln!(fmt, "{:>5} {} {}", "", bullet, Paint::new(phr))
178 }
179 Subtitle::Bold(color, phr) => {
180 let bullet = color.colorize(config.chars.bullet);
181 writeln!(fmt, "{:>5} {} {}", "", bullet, Paint::new(phr).bold())
182 }
183 Subtitle::Phrase(color, words) => {
184 let bullet = color.colorize(config.chars.bullet);
185 write!(fmt, "{:>5} {} ", "", bullet)?;
186 Renderable::<Classic>::render(words, fmt, cache, config)?;
187 writeln!(fmt)
188 }
189 Subtitle::LineBreak => {
190 writeln!(fmt)
191 }
192 }
193 }
194}
195
196impl Renderable<Classic> for Word {
197 fn render(&self, fmt: &mut dyn Write, _: &dyn FileCache, _: &RenderConfig) -> Res {
198 match self {
199 Word::Normal(str) => write!(fmt, "{} ", Paint::new(str)),
200 Word::Dimmed(str) => write!(fmt, "{} ", Paint::new(str).dimmed()),
201 Word::White(str) => write!(fmt, "{} ", Paint::new(str).bold()),
202 Word::Painted(color, str) => write!(fmt, "{} ", color.colorize(str)),
203 }
204 }
205}
206
207impl<'a> Renderable<Classic> for Subtitles<'a> {
208 fn render(&self, fmt: &mut dyn Write, cache: &dyn FileCache, config: &RenderConfig) -> Res {
209 if !self.0.is_empty() {
210 writeln!(fmt)?;
211 }
212
213 Renderable::<Classic>::render(self.0, fmt, cache, config)
214 }
215}
216
217impl<'a> Renderable<Classic> for CodeBlock<'a> {
218 fn render(&self, fmt: &mut dyn Write, _: &dyn FileCache, config: &RenderConfig) -> Res {
219 let guide = LineGuide::get(self.code);
220 let point = guide.find(self.markers.0[0].position.start);
221
222 let chars = config.chars;
223
224 let bars = chars.hbar.to_string().repeat(2);
227 let file = self.path.to_str().unwrap();
228 let header = format!("{:>5} {}{}[{}:{}]", "", chars.brline, bars, file, point);
229
230 writeln!(fmt, "{}", paint_line(header))?;
231
232 if self.markers.0.iter().all(|x| x.no_code) {
233 return Ok(());
234 }
235
236 writeln!(fmt, "{:>5} {}", "", paint_line(chars.vbar))?;
237
238 let (lines_set, mut by_line, multi_line) = group_marker_lines(&guide, self.markers);
239
240 let code_lines: Vec<&'a str> = self.code.lines().collect();
241
242 let mut lines: Vec<usize> = lines_set
243 .into_iter()
244 .filter(|x| *x < code_lines.len())
245 .collect();
246
247 lines.sort();
248
249 for i in 0..lines.len() {
250 let line = lines[i];
251 let mut prefix = " ".to_string();
252 let mut empty_vec = Vec::new();
253 let row = by_line.get_mut(&line).unwrap_or(&mut empty_vec);
254
255 let mut inline_markers: Vec<&(Point, Point, &Marker)> =
256 row.iter().filter(|x| x.0.line == x.1.line).collect();
257
258 let mut current = None;
259
260 for marker in &multi_line {
261 if marker.0.line == line {
262 writeln!(
263 fmt,
264 "{:>5} {} {} ",
265 "",
266 paint_line(config.chars.vbar),
267 marker.2.color.colorize(config.chars.brline)
268 )?;
269 }
270 if line >= marker.0.line && line <= marker.1.line {
271 prefix = format!(" {} ", marker.2.color.colorize(config.chars.vbar));
272 current = Some(marker);
273 break;
274 }
275 }
276
277 write!(
278 fmt,
279 "{:>5} {} {}",
280 line + 1,
281 paint_line(config.chars.vbar),
282 prefix,
283 )?;
284
285 let modify: Box<dyn Fn(&str) -> String> = if let Some(marker) = current {
286 prefix = format!(" {} ", marker.2.color.colorize(config.chars.vbar));
287 Box::new(|str: &str| marker.2.color.colorize(str).to_string())
288 } else {
289 Box::new(|str: &str| str.to_string())
290 };
291
292 if !inline_markers.is_empty() {
293 colorize_code(&mut inline_markers, code_lines[line], &modify, fmt)?;
294 mark_inlined(&prefix, code_lines[line], config, &mut inline_markers, fmt)?;
295 if by_line.contains_key(&(line + 1)) {
296 writeln!(
297 fmt,
298 "{:>5} {} {} ",
299 "",
300 paint_line(config.chars.dbar),
301 prefix
302 )?;
303 }
304 } else {
305 writeln!(fmt, "{}", modify(code_lines[line]))?;
306 }
307
308 if let Some(marker) = current {
309 if marker.1.line == line {
310 let col = marker.2.color.colorizer();
311 writeln!(
312 fmt,
313 "{:>5} {} {} ",
314 "",
315 paint_line(config.chars.dbar),
316 prefix
317 )?;
318 writeln!(
319 fmt,
320 "{:>5} {} {} ",
321 "",
322 paint_line(config.chars.dbar),
323 col(format!(" {} {}", config.chars.trline, marker.2.text))
324 )?;
325 prefix = " ".to_string();
326 }
327 }
328
329 if i < lines.len() - 1 && lines[i + 1] - line > 1 {
330 writeln!(
331 fmt,
332 "{:>5} {} {} ",
333 "",
334 paint_line(config.chars.dbar),
335 prefix
336 )?;
337 }
338 }
339
340 Ok(())
341 }
342}
343
344impl Renderable<Classic> for Log {
345 fn render(&self, fmt: &mut dyn Write, _: &dyn FileCache, _: &RenderConfig) -> Res {
346 match self {
347 Log::Checking(file) => {
348 writeln!(
349 fmt,
350 " {} {}",
351 Paint::new(" CHECKING ").bg(yansi::Color::Green).bold(),
352 file
353 )
354 }
355 Log::Compiled(duration) => {
356 writeln!(
357 fmt,
358 " {} All relevant terms compiled. took {:.2}s",
359 Paint::new(" COMPILED ").bg(yansi::Color::Green).bold(),
360 duration.as_secs_f32()
361 )
362 }
363 Log::Checked(duration) => {
364 writeln!(
365 fmt,
366 " {} All terms checked. took {:.2}s",
367 Paint::new(" CHECKED ").bg(yansi::Color::Green).bold(),
368 duration.as_secs_f32()
369 )
370 }
371 Log::Failed(duration, total, hidden) => {
372 writeln!(
373 fmt,
374 " {} Took {:.1}s, {} errors{}",
375 Paint::new(" FAILED ").bg(yansi::Color::Red).bold(),
376 duration.as_secs_f32(),
377 total,
378 if *hidden == 0 {
379 "".to_string()
380 } else {
381 format!(", {} hidden", hidden)
382 },
383 )
384 }
385 Log::Rewrites(u64) => {
386 writeln!(
387 fmt,
388 " {} Rewrites: {}",
389 Paint::new(" STATS ").bg(yansi::Color::Green).bold(),
390 u64
391 )
392 }
393 Log::Empty => writeln!(fmt),
394 }
395 }
396}
397
398impl<'a> Renderable<Classic> for Markers<'a> {
399 fn render(&self, fmt: &mut dyn Write, cache: &dyn FileCache, config: &RenderConfig) -> Res {
400 let groups = group_markers(self.0);
401 let is_empty = groups.is_empty();
402 let current = PathBuf::from(".").canonicalize().unwrap();
403
404 for (ctx, markers) in groups.iter() {
405 writeln!(fmt)?;
406
407 let (file, code) = cache.fetch(*ctx).unwrap();
408 let path = diff_paths(&file.clone(), current.clone()).unwrap_or(file);
409
410 let block = CodeBlock {
411 code,
412 path: &path,
413 markers,
414 };
415
416 Renderable::<Classic>::render(&block, fmt, cache, config)?;
417 }
418
419 if !is_empty {
420 writeln!(fmt)?;
421 }
422
423 Ok(())
424 }
425}
426
427impl<'a> Renderable<Classic> for Hints<'a> {
428 fn render(&self, fmt: &mut dyn Write, _: &dyn FileCache, _: &RenderConfig) -> Res {
429 for hint in self.0 {
430 writeln!(
431 fmt,
432 "{:>5} {} {}",
433 "",
434 Paint::new("Hint:").fg(yansi::Color::Cyan).bold(),
435 Paint::new(hint).fg(yansi::Color::Cyan)
436 )?;
437 }
438
439 writeln!(fmt)
440 }
441}
442
443impl Renderable<Classic> for DiagnosticFrame {
444 fn render(&self, fmt: &mut dyn Write, cache: &dyn FileCache, config: &RenderConfig) -> Res {
445 write!(fmt, " ")?;
446
447 Renderable::<Classic>::render(&self.header(), fmt, cache, config)?;
448 Renderable::<Classic>::render(&self.subtitles(), fmt, cache, config)?;
449 Renderable::<Classic>::render(&self.markers(), fmt, cache, config)?;
450 Renderable::<Classic>::render(&self.hints(), fmt, cache, config)?;
451
452 Ok(())
453 }
454}