1use 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
17pub 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
35pub 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 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
83fn label(world: &dyn SourceWorld, span: Span) -> Option<Label<FileId>> {
85 Some(Label::primary(span.id()?, world.source_range(span)?))
86}