tinymist_world/
diag.rs

1//! The diagnostic utilities.
2
3use ecow::EcoString;
4
5use std::str::FromStr;
6
7use codespan_reporting::diagnostic::{Diagnostic, Label};
8use codespan_reporting::term;
9use codespan_reporting::term::termcolor::{NoColor, WriteColor};
10use tinymist_std::Result;
11use tinymist_vfs::FileId;
12use typst::diag::{Severity, SourceDiagnostic, StrResult, eco_format};
13use typst::syntax::Span;
14
15use crate::{CodeSpanReportWorld, DiagnosticFormat, SourceWorld};
16
17/// Prints diagnostic messages to a string.
18pub fn print_diagnostics_to_string<'d, 'files>(
19    world: &'files dyn SourceWorld,
20    errors: impl Iterator<Item = &'d SourceDiagnostic>,
21    diagnostic_format: DiagnosticFormat,
22) -> StrResult<EcoString> {
23    let mut w = NoColor::new(vec![]);
24
25    print_diagnostics_to(world, errors, &mut w, diagnostic_format)
26        .map_err(|e| eco_format!("failed to print diagnostics to string: {e}"))?;
27    let output = EcoString::from_str(
28        std::str::from_utf8(&w.into_inner())
29            .map_err(|e| eco_format!("failed to convert diagnostics to string: {e}"))?,
30    )
31    .unwrap_or_default();
32    Ok(output)
33}
34
35/// Prints diagnostic messages to a writer.
36pub fn print_diagnostics_to<'d, 'files>(
37    world: &'files dyn SourceWorld,
38    errors: impl Iterator<Item = &'d SourceDiagnostic>,
39    w: &mut impl WriteColor,
40    diagnostic_format: DiagnosticFormat,
41) -> Result<(), codespan_reporting::files::Error> {
42    let world = CodeSpanReportWorld::new(world);
43
44    let mut config = term::Config {
45        tab_width: 2,
46        ..Default::default()
47    };
48    if diagnostic_format == DiagnosticFormat::Short {
49        config.display_style = term::DisplayStyle::Short;
50    }
51
52    for diagnostic in errors {
53        let diag = match diagnostic.severity {
54            Severity::Error => Diagnostic::error(),
55            Severity::Warning => Diagnostic::warning(),
56        }
57        .with_message(diagnostic.message.clone())
58        .with_notes(
59            diagnostic
60                .hints
61                .iter()
62                .map(|e| (eco_format!("hint: {e}")).into())
63                .collect(),
64        )
65        .with_labels(label(world.world, diagnostic.span).into_iter().collect());
66
67        term::emit(w, &config, &world, &diag)?;
68
69        // Stacktrace-like helper diagnostics.
70        for point in &diagnostic.trace {
71            let message = point.v.to_string();
72            let help = Diagnostic::help()
73                .with_message(message)
74                .with_labels(label(world.world, point.span).into_iter().collect());
75
76            term::emit(w, &config, &world, &help)?;
77        }
78    }
79
80    Ok(())
81}
82
83/// Creates a label for a span.
84fn label(world: &dyn SourceWorld, span: Span) -> Option<Label<FileId>> {
85    Some(Label::primary(span.id()?, world.source_range(span)?))
86}