1use crate::token::trace::{self, SourceCodeTrace};
2use crate::{error, token};
3use std::collections::HashMap;
4use texcraft_stdext::color::{self, Colorize};
5
6pub fn format_error(
7 f: &mut std::fmt::Formatter<'_>,
8 err: &error::TracedTexError,
9) -> std::fmt::Result {
10 let root = err.error.as_ref();
11 let stack = &err.stack_trace;
12
13 let (error_line, immediate_command) = match root.kind() {
14 error::Kind::Token(s) => (err.token_traces.get(&s).unwrap(), stack.last()),
15 error::Kind::EndOfInput => (err.end_of_input_trace.as_ref().unwrap(), stack.last()),
16 error::Kind::FailedPrecondition => (
17 match err.error.source_code_trace_override() {
18 None => &stack.last().unwrap().trace,
19 Some(trace) => trace,
20 },
21 None,
22 ),
23 };
24
25 let line = PrimaryLine {
26 kind: PrimaryLineKind::Error,
27 source: error_line,
28 title: root.title(),
29 token_annotation: root.source_annotation(),
30 notes: root
31 .notes()
32 .iter()
33 .map(|n| n.trace(&err.token_traces))
34 .map(|n| format!("{n}"))
35 .collect(),
36 };
37 write!(f, "{line}")?;
38
39 if let Some(err) = immediate_command {
40 let line = PrimaryLine {
41 kind: PrimaryLineKind::Context,
42 source: &err.trace,
43 title: format!["this error occurred while {}:", err.context.action()],
44 token_annotation: "".into(),
45 notes: vec![format![
46 "this is the full stack trace of the error:\n\n{}",
47 ErrorStack(stack)
48 ]],
49 };
50 write!(f, "\n{line}")?;
51 }
52
53 Ok(())
54}
55
56#[derive(Debug)]
57struct PrimaryLine<'a> {
58 kind: PrimaryLineKind,
59 source: &'a trace::SourceCodeTrace,
60 title: String,
61 token_annotation: String,
62 notes: Vec<String>,
63}
64
65#[derive(Debug, Clone, Copy)]
66enum PrimaryLineKind {
67 Error,
68 Context,
69}
70
71impl PrimaryLineKind {
72 fn color(&self, s: &str) -> color::ColoredString {
73 match self {
74 PrimaryLineKind::Error => s.bright_red(),
75 PrimaryLineKind::Context => s.bright_yellow(),
76 }
77 }
78}
79
80impl std::fmt::Display for PrimaryLineKind {
81 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82 write!(
83 f,
84 "{}",
85 match self {
86 PrimaryLineKind::Error => "Error".bright_red(),
87 PrimaryLineKind::Context => "Context".bright_yellow(),
88 }
89 .bold()
90 )
91 }
92}
93
94impl<'a> std::fmt::Display for PrimaryLine<'a> {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 let margin_width = self.source.line_number.to_string().len() + 1;
97 let printer = Printer {
98 indent: margin_width,
99 };
100 writeln!(f, "{}: {}", self.kind, self.title.bold())?;
101 fmt_source_code_trace(&printer, f, self.source, &self.token_annotation, self.kind)?;
102
103 for (i, note) in self.notes.iter().enumerate() {
104 let mut note_lines = note.trim_end().lines();
105 let first_note_line = match note_lines.next() {
106 None => continue,
107 Some(s) => s,
108 };
109 printer.new_line().print(f)?;
110 printer
111 .new_line()
112 .with_separator('=')
113 .with_content(format!["{} {}", "note:".bold(), first_note_line])
114 .print(f)?;
115 for line in note_lines {
116 let mut l = printer.new_line().with_content(format![" {line}"]);
117 if i == self.notes.len() - 1 {
118 l = l.with_separator(' ');
119 }
120 l.print(f)?;
121 }
122 }
123
124 Ok(())
125 }
126}
127
128struct ErrorStack<'a>(&'a Vec<error::StackTraceElement>);
129
130impl<'a> std::fmt::Display for ErrorStack<'a> {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 for (u, propagated) in self.0.iter().rev().enumerate() {
133 if u > 0 {
134 writeln!(f)?;
135 }
136 let (s, p) = (&propagated.trace, &propagated.context);
137 fmt_source_code_trace_light(f, s, 2, PrimaryLineKind::Context, p.action())?;
138 }
139 Ok(())
140 }
141}
142
143struct Printer {
144 indent: usize,
145}
146
147struct PrintLineBuilder<'a> {
148 printer: &'a Printer,
149 margin_content: String,
150 separator: Option<char>,
151 content: String,
152 indent_adjustment: usize,
153}
154
155impl Printer {
156 fn new_line(&self) -> PrintLineBuilder<'_> {
157 PrintLineBuilder {
158 printer: self,
159 margin_content: "".into(),
160 separator: Some('|'),
161 content: "".into(),
162 indent_adjustment: 0,
163 }
164 }
165}
166
167impl<'a> PrintLineBuilder<'a> {
168 fn with_content<T: Into<String>>(mut self, content: T) -> Self {
169 self.content = content.into();
170 self
171 }
172 fn with_margin_content<T: Into<String>>(mut self, content: T) -> Self {
173 self.margin_content = content.into();
174 self
175 }
176 fn with_indent_adjustment(mut self, u: usize) -> Self {
177 self.indent_adjustment = u;
178 self
179 }
180 fn with_separator(mut self, c: char) -> Self {
181 self.separator = Some(c);
182 self
183 }
184 fn without_separator(mut self) -> Self {
185 self.separator = None;
186 self
187 }
188
189 fn print(self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190 let indent: usize = self
191 .printer
192 .indent
193 .saturating_sub(self.indent_adjustment)
194 .saturating_sub(self.margin_content.len() + 1);
195 let margin_content = format!["{}{} ", " ".repeat(indent), self.margin_content];
196 let separator = match self.separator {
197 None => "".to_string(),
198 Some(c) => format!["{c} "],
199 };
200 writeln!(
201 f,
202 "{}{}{}",
203 margin_content.bright_cyan(),
204 separator.bright_cyan(),
205 self.content
206 )
207 }
208}
209
210fn fmt_source_code_trace(
211 printer: &Printer,
212 f: &mut std::fmt::Formatter<'_>,
213 s: &trace::SourceCodeTrace,
214 annotation: &str,
215 line_kind: PrimaryLineKind,
216) -> std::fmt::Result {
217 printer
218 .new_line()
219 .without_separator()
220 .with_indent_adjustment(1)
221 .with_content(format!(
222 "{} {}:{}:{}",
223 ">>>".bright_cyan().bold(),
224 match &s.origin {
225 trace::Origin::File(file_name) => format!["{}", file_name.display()],
226 trace::Origin::Terminal => "<input from terminal>".to_string(),
227 },
228 s.line_number,
229 s.index + 1
230 ))
231 .print(f)?;
232 printer.new_line().print(f)?;
233 printer
234 .new_line()
235 .with_margin_content(format!["{}", s.line_number])
236 .with_content(highlight_substring(&s.line_content, s.index, s.value.len()))
237 .print(f)?;
238 printer
239 .new_line()
240 .with_content(format![
241 "{}{} {}",
242 " ".repeat(s.index),
243 line_kind.color(&"^".repeat(s.value.len())).bold(),
244 line_kind.color(annotation).bold(),
245 ])
246 .print(f)?;
247 Ok(())
248}
249
250fn highlight_substring(line: &str, start: usize, length: usize) -> String {
251 if line.len() < start + length {
252 return line.into();
253 }
254 format![
255 "{}{}{}",
256 &line[..start],
257 (&line[start..start + length]).bold(),
258 line[start + length..].trim_end(),
259 ]
260}
261
262fn fmt_source_code_trace_light(
263 f: &mut std::fmt::Formatter<'_>,
264 s: &SourceCodeTrace,
265 indent: usize,
266 line_kind: PrimaryLineKind,
267 annotation: &str,
268) -> std::fmt::Result {
269 let prefix = format!(
270 "{}{}:{}:{}",
271 " ".repeat(indent),
272 match &s.origin {
273 trace::Origin::File(file_name) => format!["{}", file_name.display()],
274 trace::Origin::Terminal => "<input from terminal>".to_string(),
275 },
276 s.line_number,
277 s.index + 1
278 );
279 writeln!(
280 f,
281 "{} {}",
282 prefix,
283 highlight_substring(&s.line_content, s.index, s.value.len())
284 )?;
285 writeln!(
286 f,
287 "{} {} {}",
288 " ".repeat(prefix.len() + s.index),
289 line_kind.color(&"^".repeat(s.value.len())).bold(),
290 annotation,
291 )?;
292 Ok(())
293}
294
295#[derive(Clone, Debug)]
296#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
297pub enum Note {
298 Text(String),
299 SourceCodeTrace(String, token::Token),
300}
301
302impl Note {
303 fn trace<'a>(&'a self, m: &'a HashMap<token::Token, trace::SourceCodeTrace>) -> TracedNote<'a> {
304 match self {
305 Note::Text(t) => TracedNote::Text(t),
306 Note::SourceCodeTrace(s, token) => TracedNote::SourceCodeTrace(
307 s,
308 m.get(token)
309 .expect("all tokens in the error have been traced"),
310 ),
311 }
312 }
313}
314
315impl<T: Into<String>> From<T> for Note {
316 fn from(value: T) -> Self {
317 Note::Text(value.into())
318 }
319}
320
321#[derive(Debug)]
322enum TracedNote<'a> {
323 Text(&'a str),
324 SourceCodeTrace(&'a str, &'a trace::SourceCodeTrace),
325}
326
327impl<'a> std::fmt::Display for TracedNote<'a> {
328 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
329 match self {
330 TracedNote::Text(s) => write!(f, "{s}"),
331 TracedNote::SourceCodeTrace(s, trace) => {
332 write!(f, "{s}\n\n")?;
333 fmt_source_code_trace_light(f, trace, 2, PrimaryLineKind::Error, "")
334 }
335 }
336 }
337}