aboutsummaryrefslogtreecommitdiff
path: root/mingling_cli/src/diagnostic.rs
blob: 527e87978203e2222590b1f1d37a797da25a4150 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
use annotate_snippets::level::{ERROR, HELP, NOTE, WARNING};
use annotate_snippets::{AnnotationKind, Group, Renderer, Snippet};
use cargo_metadata::diagnostic::Diagnostic;
use mingling::macros::{buffer, group, r_println, renderer};

group!(Diagnostic);

#[renderer(buffer)]
pub fn render_diagnostic(diagnostic: Diagnostic) {
    let report = diagnostic_to_report(&diagnostic);
    let renderer = Renderer::styled();
    let rendered = renderer.render(&report);
    r_println!("{rendered}");
}

fn cargo_level_to_annotate(
    level: cargo_metadata::diagnostic::DiagnosticLevel,
) -> &'static annotate_snippets::Level<'static> {
    use cargo_metadata::diagnostic::DiagnosticLevel;
    match level {
        DiagnosticLevel::Ice | DiagnosticLevel::Error => &ERROR,
        DiagnosticLevel::Warning => &WARNING,
        DiagnosticLevel::Note | DiagnosticLevel::FailureNote => &NOTE,
        DiagnosticLevel::Help => &HELP,
        _ => &ERROR,
    }
}

/// 把 1-based char offset 转成 0-based byte offset
fn char_offset_to_byte_offset(s: &str, char_offset: usize) -> usize {
    s.char_indices()
        .nth(char_offset.saturating_sub(1))
        .map(|(i, _)| i)
        .unwrap_or(s.len())
}

fn diagnostic_to_report<'a>(d: &'a Diagnostic) -> Vec<Group<'a>> {
    let level = cargo_level_to_annotate(d.level);
    let mut title = level.clone().primary_title(d.message.as_str());
    if let Some(ref code) = d.code {
        title = title.id(code.code.as_str());
    }

    let mut group = Group::with_title(title);

    for span in &d.spans {
        if !span.is_primary {
            continue;
        }

        let source: String = span
            .text
            .iter()
            .map(|l| l.text.as_str())
            .collect::<Vec<_>>()
            .join("\n");

        let byte_range = if span.text.len() == 1 {
            let line = &span.text[0];
            let start = char_offset_to_byte_offset(&line.text, line.highlight_start);
            let end = char_offset_to_byte_offset(&line.text, line.highlight_end);
            start..end
        } else {
            let first = &span.text[0];
            let last = span.text.last().unwrap();
            let start = char_offset_to_byte_offset(&first.text, first.highlight_start);
            let prefix_len: usize = span.text[..span.text.len() - 1]
                .iter()
                .map(|l| l.text.len() + 1)
                .sum();
            let end = prefix_len + char_offset_to_byte_offset(&last.text, last.highlight_end);
            start..end
        };

        let mut snippet = Snippet::source(source)
            .line_start(span.line_start)
            .path(span.file_name.as_str());

        if let Some(ref label) = span.label {
            let annotation = AnnotationKind::Primary
                .span(byte_range)
                .label(label.as_str());
            snippet = snippet.annotation(annotation);
        } else {
            let annotation = AnnotationKind::Primary.span(byte_range);
            snippet = snippet.annotation(annotation);
        }

        group = group.element(snippet);
    }

    for child in &d.children {
        let msg_level = cargo_level_to_annotate(child.level);
        let msg = msg_level.clone().message(child.message.as_str());
        group = group.element(msg);
    }

    vec![group]
}