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::WorldExt;
13use typst::diag::{Severity, SourceDiagnostic, StrResult, eco_format};
14use typst::syntax::DiagSpan;
15
16use crate::{CodeSpanReportWorld, DiagnosticFormat, SourceWorld};
17
18pub 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
36pub 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 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
84fn 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}