plc_diagnostics/reporter/
codespan.rs1use codespan_reporting::{
2 diagnostic::{Diagnostic, Label},
3 files::SimpleFiles,
4 term::termcolor::{Buffer, ColorChoice, StandardStream, WriteColor},
5};
6use plc_source::source_location::CodeSpan;
7
8use crate::diagnostics::Severity;
9
10use super::{DiagnosticReporter, ResolvedDiagnostics};
11
12enum Writer {
13 Buffer(Buffer),
15
16 Stream(StandardStream),
18}
19
20impl std::io::Write for Writer {
21 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
22 match self {
23 Writer::Buffer(writer) => writer.write(buf),
24 Writer::Stream(writer) => writer.write(buf),
25 }
26 }
27
28 fn flush(&mut self) -> std::io::Result<()> {
29 match self {
30 Writer::Buffer(writer) => writer.flush(),
31 Writer::Stream(writer) => writer.flush(),
32 }
33 }
34}
35
36impl WriteColor for Writer {
37 fn supports_color(&self) -> bool {
38 match self {
39 Writer::Buffer(writer) => writer.supports_color(),
40 Writer::Stream(writer) => writer.supports_color(),
41 }
42 }
43
44 fn set_color(&mut self, spec: &codespan_reporting::term::termcolor::ColorSpec) -> std::io::Result<()> {
45 match self {
46 Writer::Buffer(writer) => writer.set_color(spec),
47 Writer::Stream(writer) => writer.set_color(spec),
48 }
49 }
50
51 fn reset(&mut self) -> std::io::Result<()> {
52 match self {
53 Writer::Buffer(writer) => writer.reset(),
54 Writer::Stream(writer) => writer.reset(),
55 }
56 }
57}
58
59pub struct CodeSpanDiagnosticReporter {
61 files: SimpleFiles<String, String>,
62 config: codespan_reporting::term::Config,
63 writer: Writer,
64}
65
66impl CodeSpanDiagnosticReporter {
67 pub(crate) fn terminal(config: codespan_reporting::term::Config, writer: StandardStream) -> Self {
69 CodeSpanDiagnosticReporter { files: SimpleFiles::new(), config, writer: Writer::Stream(writer) }
70 }
71
72 pub(crate) fn buffered() -> CodeSpanDiagnosticReporter {
74 CodeSpanDiagnosticReporter { writer: Writer::Buffer(Buffer::no_color()), ..Default::default() }
75 }
76
77 fn emit(&mut self, diag: Diagnostic<usize>) -> Result<(), codespan_reporting::files::Error> {
78 codespan_reporting::term::emit(&mut self.writer, &self.config, &self.files, &diag)
79 }
80
81 fn file_name(&self, handle: usize) -> String {
82 use codespan_reporting::files::Files;
83 self.files.name(handle).map(|name| name.to_string()).unwrap_or_default()
84 }
85}
86
87impl Default for CodeSpanDiagnosticReporter {
88 fn default() -> Self {
90 Self::terminal(
91 codespan_reporting::term::Config {
92 display_style: codespan_reporting::term::DisplayStyle::Rich,
93 tab_width: 2,
94 styles: codespan_reporting::term::Styles::default(),
95 chars: codespan_reporting::term::Chars::default(),
96 start_context_lines: 5,
97 end_context_lines: 3,
98 before_label_lines: 0,
99 after_label_lines: 0,
100 },
101 StandardStream::stderr(ColorChoice::Always),
102 )
103 }
104}
105
106impl DiagnosticReporter for CodeSpanDiagnosticReporter {
107 fn report(&mut self, diagnostics: &[ResolvedDiagnostics]) {
108 for d in diagnostics {
109 let diagnostic_factory = match d.severity {
110 Severity::Error => codespan_reporting::diagnostic::Diagnostic::error(),
111 Severity::Warning => codespan_reporting::diagnostic::Diagnostic::warning(),
112 Severity::Info => codespan_reporting::diagnostic::Diagnostic::note(),
113 Severity::Ignore => {
114 continue;
116 }
117 };
118
119 let mut labels = vec![];
120 let mut notes = vec![];
121
122 match &d.main_location.span {
125 CodeSpan::None => {}
126 span => match span.to_range() {
127 Some(range) => labels.push(
128 Label::primary(d.main_location.file_handle, range).with_message(d.message.as_str()),
129 ),
130 None => notes.push(format!("{}: {}", self.file_name(d.main_location.file_handle), span)),
131 },
132 }
133
134 if let Some(additional_locations) = &d.additional_locations {
135 labels.extend(additional_locations.iter().filter_map(|it| {
136 it.span
137 .to_range()
138 .map(|range| Label::secondary(it.file_handle, range).with_message("see also"))
139 }));
140 }
141
142 let diag = diagnostic_factory
143 .with_labels(labels)
144 .with_notes(notes)
145 .with_message(d.message.as_str())
146 .with_code(&d.code);
147
148 let result = self.emit(diag);
149 if result.is_err() && d.main_location.is_internal() {
150 eprintln!("<internal>: {}", d.message);
151 }
152 }
153 }
154
155 fn register(&mut self, path: String, src: String) -> usize {
156 self.files.add(path, src)
157 }
158
159 fn buffer(&self) -> Option<String> {
160 match &self.writer {
161 Writer::Buffer(buffer) => Some(String::from_utf8_lossy(buffer.as_slice()).to_string()),
162 Writer::Stream(_) => None,
163 }
164 }
165}