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