1use crate::{Backtraced, INDENT};
18
19use leo_span::{Span, source_map::LineContents, with_session_globals};
20
21use backtrace::Backtrace;
22use color_backtrace::{BacktracePrinter, Verbosity};
23use colored::Colorize;
24use itertools::Itertools;
25use std::fmt;
26
27#[derive(Default, Clone, Debug, Hash, PartialEq, Eq)]
29pub enum Color {
30 #[default]
31 Red,
32 Yellow,
33 Blue,
34 Green,
35 Cyan,
36 Magenta,
37}
38
39impl Color {
40 pub fn color_and_bold(&self, text: &str, use_colors: bool) -> String {
42 if use_colors {
43 match self {
44 Color::Red => text.bold().red().to_string(),
45 Color::Yellow => text.bold().yellow().to_string(),
46 Color::Blue => text.bold().blue().to_string(),
47 Color::Green => text.bold().green().to_string(),
48 Color::Cyan => text.bold().cyan().to_string(),
49 Color::Magenta => text.bold().magenta().to_string(),
50 }
51 } else {
52 text.to_string()
53 }
54 }
55}
56
57#[derive(Clone, Debug, Hash, PartialEq, Eq)]
59pub struct Label {
60 msg: String,
61 span: Span,
62 color: Color,
63}
64
65impl Label {
66 pub fn new(msg: impl fmt::Display, span: Span) -> Self {
67 Self { msg: msg.to_string(), span, color: Color::default() }
68 }
69
70 pub fn with_color(mut self, color: Color) -> Self {
71 self.color = color;
72 self
73 }
74}
75
76#[derive(Clone, Debug, Hash, PartialEq, Eq)]
86pub struct Formatted {
87 pub span: Span,
89 pub labels: Vec<Label>,
91 pub backtrace: Box<Backtraced>,
93}
94
95impl Formatted {
96 #[allow(clippy::too_many_arguments)]
98 pub fn new_from_span<S>(
99 message: S,
100 help: Option<String>,
101 code: i32,
102 code_identifier: i8,
103 type_: String,
104 error: bool,
105 span: Span,
106 backtrace: Backtrace,
107 ) -> Self
108 where
109 S: ToString,
110 {
111 Self {
112 span,
113 labels: Vec::new(),
114 backtrace: Box::new(Backtraced::new_from_backtrace(
115 message.to_string(),
116 help,
117 code,
118 code_identifier,
119 type_,
120 error,
121 backtrace,
122 )),
123 }
124 }
125
126 pub fn exit_code(&self) -> i32 {
128 self.backtrace.exit_code()
129 }
130
131 pub fn error_code(&self) -> String {
133 self.backtrace.error_code()
134 }
135
136 pub fn warning_code(&self) -> String {
138 self.backtrace.warning_code()
139 }
140
141 pub fn with_labels(mut self, labels: Vec<Label>) -> Self {
143 self.labels = labels;
144 self
145 }
146}
147
148fn compute_line_spans(lc: &LineContents) -> Vec<(usize, usize)> {
150 let lines: Vec<&str> = lc.contents.lines().collect();
151 let mut byte_index = 0;
152 let mut line_spans = Vec::new();
153
154 for line in &lines {
155 let line_start = byte_index;
156 let line_end = byte_index + line.len();
157 let start = lc.start.saturating_sub(line_start);
158 let end = lc.end.saturating_sub(line_start);
159 line_spans.push((start.min(line.len()), end.min(line.len())));
160 byte_index = line_end + 1; }
162
163 line_spans
164}
165
166fn print_gap(
168 f: &mut impl std::fmt::Write,
169 prev_last_line: Option<usize>,
170 first_line_of_block: usize,
171) -> std::fmt::Result {
172 if let Some(prev_last) = prev_last_line {
173 let gap = first_line_of_block.saturating_sub(prev_last + 1);
174 if gap == 1 {
175 writeln!(f, "{:width$} |", prev_last + 1, width = INDENT.len())?;
177 } else if gap > 1 {
178 writeln!(f, "{:width$}...", "", width = INDENT.len() - 1)?;
180 }
181 }
182 Ok(())
183}
184
185#[allow(clippy::too_many_arguments)]
187fn print_code_line(
188 f: &mut impl std::fmt::Write,
189 line_num: usize,
190 line_text: &str,
191 connector: &str,
192 start: usize,
193 end: usize,
194 multiline: bool,
195 first_line: Option<usize>,
196 last_line: Option<usize>,
197 label: &Label,
198) -> std::fmt::Result {
199 let use_colors = std::env::var("NOCOLOR").unwrap_or_default().trim().to_owned().is_empty();
200
201 write!(f, "{:width$} | {} ", line_num, label.color.color_and_bold(connector, use_colors), width = INDENT.len())?;
203 writeln!(f, "{line_text}")?;
204
205 if !multiline && end > start {
207 writeln!(
208 f,
209 "{INDENT} | {:start$}{} {}",
210 "",
211 label.color.color_and_bold(&"^".repeat(end - start), use_colors),
212 label.color.color_and_bold(&label.msg, use_colors),
213 start = start
214 )?;
215 }
216 else if multiline
218 && let (Some(first), Some(last)) = (first_line, last_line)
219 && line_num - first_line.unwrap() == last - first
220 {
221 let underline_len = (end - start).max(1);
222 writeln!(
223 f,
224 "{INDENT} | {:start$}{} {}",
225 label.color.color_and_bold("|", use_colors), label.color.color_and_bold(&"_".repeat(underline_len), use_colors), label.color.color_and_bold(&label.msg, use_colors), start = start
229 )?;
230 }
231
232 Ok(())
233}
234
235fn print_multiline_underline(
237 f: &mut impl std::fmt::Write,
238 start_col: usize,
239 end_col: usize,
240 label: &Label,
241) -> std::fmt::Result {
242 let use_colors = std::env::var("NOCOLOR").unwrap_or_default().trim().to_owned().is_empty();
243 let underline_len = (end_col - start_col).max(1);
244 let underline = format!("{}-", "_".repeat(underline_len));
245
246 writeln!(
247 f,
248 "{INDENT} | {:start$}{} {}",
249 label.color.color_and_bold("|", use_colors), label.color.color_and_bold(&underline, use_colors), label.color.color_and_bold(&label.msg, use_colors), start = start_col
253 )
254}
255
256impl fmt::Display for Formatted {
257 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
258 let (kind, code) =
259 if self.backtrace.error { ("Error", self.error_code()) } else { ("Warning", self.warning_code()) };
260
261 let message = format!("{kind} [{code}]: {message}", message = self.backtrace.message,);
262
263 if std::env::var("NOCOLOR").unwrap_or_default().trim().to_owned().is_empty() {
265 if self.backtrace.error {
266 writeln!(f, "{}", message.bold().red())?;
267 } else {
268 writeln!(f, "{}", message.bold().yellow())?;
269 }
270 } else {
271 writeln!(f, "{message}")?;
272 };
273
274 if let Some(source_file) = with_session_globals(|s| s.source_map.find_source_file(self.span.lo)) {
275 let line_contents = source_file.line_contents(self.span);
276
277 writeln!(
278 f,
279 "{indent }--> {path}:{line_start}:{start}",
280 indent = INDENT,
281 path = &source_file.name,
282 line_start = line_contents.line + 1,
284 start = line_contents.start + 1,
287 )?;
288
289 if self.labels.is_empty() {
290 write!(f, "{line_contents}")?;
292 } else {
293 let labels = self
305 .labels
306 .iter()
307 .filter_map(|label| {
308 with_session_globals(|s| s.source_map.find_source_file(label.span.lo)).map(|source_file| {
309 let lc = source_file.line_contents(label.span);
310 (label.clone(), lc.line)
311 })
312 })
313 .sorted_by_key(|(_, line)| *line)
314 .map(|(label, _)| label)
315 .collect_vec();
316
317 let mut prev_last_line: Option<usize> = None;
319
320 for label in labels {
321 let Some(source_file) = with_session_globals(|s| s.source_map.find_source_file(label.span.lo))
323 else {
324 continue;
325 };
326
327 let lc = source_file.line_contents(label.span);
329
330 let line_spans = compute_line_spans(&lc);
332
333 let first_line_of_block = lc.line + 1; print_gap(f, prev_last_line, first_line_of_block)?;
337
338 if prev_last_line.is_none() {
340 writeln!(f, "{INDENT} |")?;
341 }
342
343 let multiline = line_spans.iter().any(|&(s, e)| e > s) && lc.contents.lines().count() > 1;
345
346 let first_line = line_spans.iter().position(|&(s, e)| e > s);
348 let last_line = line_spans.iter().rposition(|&(s, e)| e > s);
349
350 for (i, (line_text, &(start, end))) in lc.contents.lines().zip(&line_spans).enumerate() {
352 let line_num = lc.line + i + 1;
353
354 let connector = if multiline {
357 match (first_line, last_line) {
358 (Some(first), Some(_last)) => {
359 if i == first {
360 "/"
361 } else {
362 "|"
363 }
364 }
365 _ => " ",
366 }
367 } else {
368 " "
369 };
370
371 print_code_line(
373 f, line_num, line_text, connector, start, end, multiline, first_line, last_line, &label,
374 )?;
375 }
376
377 if multiline && let (Some(_), Some(last)) = (first_line, last_line) {
379 let start_col = line_spans[last].0;
381
382 let end_col = line_spans[last].1;
384
385 print_multiline_underline(f, start_col, end_col, &label)?;
386 }
387
388 prev_last_line = Some(lc.line + lc.contents.lines().count());
390 }
391 }
392 }
393
394 if let Some(help) = &self.backtrace.help {
395 writeln!(
396 f,
397 "{INDENT } |\n\
398 {INDENT } = {help}",
399 )?;
400 }
401
402 let leo_backtrace = std::env::var("LEO_BACKTRACE").unwrap_or_default().trim().to_owned();
403 match leo_backtrace.as_ref() {
404 "1" => {
405 let mut printer = BacktracePrinter::default();
406 printer = printer.verbosity(Verbosity::Medium);
407 printer = printer.lib_verbosity(Verbosity::Medium);
408 let trace = printer.format_trace_to_string(&self.backtrace.backtrace).map_err(|_| fmt::Error)?;
409 write!(f, "\n{trace}")?;
410 }
411 "full" => {
412 let mut printer = BacktracePrinter::default();
413 printer = printer.verbosity(Verbosity::Full);
414 printer = printer.lib_verbosity(Verbosity::Full);
415 let trace = printer.format_trace_to_string(&self.backtrace.backtrace).map_err(|_| fmt::Error)?;
416 write!(f, "\n{trace}")?;
417 }
418 _ => {}
419 }
420
421 Ok(())
422 }
423}
424
425impl std::error::Error for Formatted {
426 fn description(&self) -> &str {
427 &self.backtrace.message
428 }
429}