kind_report/report/mode/
classic.rs

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    // Pretty print the marker
95    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        // Header of the code block
225
226        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}