Skip to main content

dada_ir_ast/diagnostic/
render.rs

1use crate::{diagnostic::Diagnostic, span::AbsoluteSpan};
2use annotate_snippets::{Message, Renderer, Snippet};
3use dada_util::arena::Arena;
4
5use super::RenderOptions;
6
7pub(super) fn render(db: &dyn crate::Db, opts: &RenderOptions, diagnostic: &Diagnostic) -> String {
8    let arena = Arena::new();
9    let message = to_message(db, diagnostic, &arena);
10    renderer(opts).render(message).to_string()
11}
12
13fn renderer(opts: &RenderOptions) -> Renderer {
14    if opts.no_color {
15        Renderer::plain()
16    } else {
17        Renderer::styled()
18    }
19}
20
21fn to_level(level: crate::diagnostic::Level) -> annotate_snippets::Level {
22    match level {
23        crate::diagnostic::Level::Note => annotate_snippets::Level::Note,
24        crate::diagnostic::Level::Warning => annotate_snippets::Level::Warning,
25        crate::diagnostic::Level::Info => annotate_snippets::Level::Info,
26        crate::diagnostic::Level::Help => annotate_snippets::Level::Help,
27        crate::diagnostic::Level::Error => annotate_snippets::Level::Error,
28    }
29}
30
31fn to_message<'a>(
32    db: &'a dyn crate::Db,
33    diagnostic: &'a Diagnostic,
34    arena: &'a Arena,
35) -> Message<'a> {
36    to_level(diagnostic.level)
37        .title(&diagnostic.message)
38        .snippet(to_snippet(db, diagnostic, arena))
39        .footers(diagnostic.children.iter().map(|d| to_message(db, d, arena)))
40}
41
42fn to_snippet<'a>(
43    db: &'a dyn crate::Db,
44    diagnostic: &'a Diagnostic,
45    arena: &'a Arena,
46) -> Snippet<'a> {
47    let source_file = diagnostic.span.source_file;
48
49    let default_label = if !diagnostic.labels.is_empty() {
50        None
51    } else {
52        Some(
53            to_level(diagnostic.level)
54                .span(to_span(diagnostic.span))
55                .label("here"),
56        )
57    };
58
59    let url = source_file.url(db);
60    let origin = arena.insert(db.url_display(url));
61
62    Snippet::source(source_file.contents_if_ok(db))
63        .line_start(1)
64        .origin(origin)
65        .fold(true)
66        .annotations(
67            diagnostic
68                .labels
69                .iter()
70                .map(|label| {
71                    assert!(label.span.source_file == source_file);
72                    to_level(label.level)
73                        .span(to_span(label.span))
74                        .label(&label.message)
75                })
76                .chain(default_label),
77        )
78}
79
80fn to_span(span: AbsoluteSpan) -> std::ops::Range<usize> {
81    span.start.as_usize()..span.end.as_usize()
82}