use crate::{Backtraced, INDENT};
use leo_span::{Span, source_map::LineContents, with_session_globals};
use backtrace::Backtrace;
use color_backtrace::{BacktracePrinter, Verbosity};
use colored::Colorize;
use itertools::Itertools;
use std::fmt;
#[derive(Default, Clone, Debug, Hash, PartialEq, Eq)]
pub enum Color {
#[default]
Red,
Yellow,
Blue,
Green,
Cyan,
Magenta,
}
impl Color {
pub fn color_and_bold(&self, text: &str, use_colors: bool) -> String {
if use_colors {
match self {
Color::Red => text.bold().red().to_string(),
Color::Yellow => text.bold().yellow().to_string(),
Color::Blue => text.bold().blue().to_string(),
Color::Green => text.bold().green().to_string(),
Color::Cyan => text.bold().cyan().to_string(),
Color::Magenta => text.bold().magenta().to_string(),
}
} else {
text.to_string()
}
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Label {
msg: String,
span: Span,
color: Color,
}
impl Label {
pub fn new(msg: impl fmt::Display, span: Span) -> Self {
Self { msg: msg.to_string(), span, color: Color::default() }
}
pub fn with_color(mut self, color: Color) -> Self {
self.color = color;
self
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Formatted {
pub span: Span,
pub labels: Vec<Label>,
pub backtrace: Box<Backtraced>,
}
impl Formatted {
#[allow(clippy::too_many_arguments)]
pub fn new_from_span<S>(
message: S,
help: Option<String>,
code: i32,
code_identifier: i8,
type_: String,
error: bool,
span: Span,
backtrace: Backtrace,
) -> Self
where
S: ToString,
{
Self {
span,
labels: Vec::new(),
backtrace: Box::new(Backtraced::new_from_backtrace(
message.to_string(),
help,
code,
code_identifier,
type_,
error,
backtrace,
)),
}
}
pub fn exit_code(&self) -> i32 {
self.backtrace.exit_code()
}
pub fn error_code(&self) -> String {
self.backtrace.error_code()
}
pub fn warning_code(&self) -> String {
self.backtrace.warning_code()
}
pub fn with_labels(mut self, labels: Vec<Label>) -> Self {
self.labels = labels;
self
}
}
fn compute_line_spans(lc: &LineContents) -> Vec<(usize, usize)> {
let lines: Vec<&str> = lc.contents.lines().collect();
let mut byte_index = 0;
let mut line_spans = Vec::new();
for line in &lines {
let line_start = byte_index;
let line_end = byte_index + line.len();
let start = lc.start.saturating_sub(line_start);
let end = lc.end.saturating_sub(line_start);
line_spans.push((start.min(line.len()), end.min(line.len())));
byte_index = line_end + 1; }
line_spans
}
fn print_gap(
f: &mut impl std::fmt::Write,
prev_last_line: Option<usize>,
first_line_of_block: usize,
) -> std::fmt::Result {
if let Some(prev_last) = prev_last_line {
let gap = first_line_of_block.saturating_sub(prev_last + 1);
if gap == 1 {
writeln!(f, "{:width$} |", prev_last + 1, width = INDENT.len())?;
} else if gap > 1 {
writeln!(f, "{:width$}...", "", width = INDENT.len() - 1)?;
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn print_code_line(
f: &mut impl std::fmt::Write,
line_num: usize,
line_text: &str,
connector: &str,
start: usize,
end: usize,
multiline: bool,
first_line: Option<usize>,
last_line: Option<usize>,
label: &Label,
) -> std::fmt::Result {
let use_colors = std::env::var("NOCOLOR").unwrap_or_default().trim().to_owned().is_empty();
write!(f, "{:width$} | {} ", line_num, label.color.color_and_bold(connector, use_colors), width = INDENT.len())?;
writeln!(f, "{line_text}")?;
if !multiline && end > start {
writeln!(
f,
"{INDENT} | {:start$}{} {}",
"",
label.color.color_and_bold(&"^".repeat(end - start), use_colors),
label.color.color_and_bold(&label.msg, use_colors),
start = start
)?;
}
else if multiline
&& let (Some(first), Some(last)) = (first_line, last_line)
&& line_num - first_line.unwrap() == last - first
{
let underline_len = (end - start).max(1);
writeln!(
f,
"{INDENT} | {:start$}{} {}",
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
)?;
}
Ok(())
}
fn print_multiline_underline(
f: &mut impl std::fmt::Write,
start_col: usize,
end_col: usize,
label: &Label,
) -> std::fmt::Result {
let use_colors = std::env::var("NOCOLOR").unwrap_or_default().trim().to_owned().is_empty();
let underline_len = (end_col - start_col).max(1);
let underline = format!("{}-", "_".repeat(underline_len));
writeln!(
f,
"{INDENT} | {:start$}{} {}",
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
)
}
impl fmt::Display for Formatted {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let (kind, code) =
if self.backtrace.error { ("Error", self.error_code()) } else { ("Warning", self.warning_code()) };
let message = format!("{kind} [{code}]: {message}", message = self.backtrace.message,);
if std::env::var("NOCOLOR").unwrap_or_default().trim().to_owned().is_empty() {
if self.backtrace.error {
writeln!(f, "{}", message.bold().red())?;
} else {
writeln!(f, "{}", message.bold().yellow())?;
}
} else {
writeln!(f, "{message}")?;
};
if let Some(source_file) = with_session_globals(|s| s.source_map.find_source_file(self.span.lo)) {
let line_contents = source_file.line_contents(self.span);
writeln!(
f,
"{indent }--> {path}:{line_start}:{start}",
indent = INDENT,
path = &source_file.name,
line_start = line_contents.line + 1,
start = line_contents.start + 1,
)?;
if self.labels.is_empty() {
write!(f, "{line_contents}")?;
} else {
let labels = self
.labels
.iter()
.filter_map(|label| {
with_session_globals(|s| s.source_map.find_source_file(label.span.lo)).map(|source_file| {
let lc = source_file.line_contents(label.span);
(label.clone(), lc.line)
})
})
.sorted_by_key(|(_, line)| *line)
.map(|(label, _)| label)
.collect_vec();
let mut prev_last_line: Option<usize> = None;
for label in labels {
let Some(source_file) = with_session_globals(|s| s.source_map.find_source_file(label.span.lo))
else {
continue;
};
let lc = source_file.line_contents(label.span);
let line_spans = compute_line_spans(&lc);
let first_line_of_block = lc.line + 1;
print_gap(f, prev_last_line, first_line_of_block)?;
if prev_last_line.is_none() {
writeln!(f, "{INDENT} |")?;
}
let multiline = line_spans.iter().any(|&(s, e)| e > s) && lc.contents.lines().count() > 1;
let first_line = line_spans.iter().position(|&(s, e)| e > s);
let last_line = line_spans.iter().rposition(|&(s, e)| e > s);
for (i, (line_text, &(start, end))) in lc.contents.lines().zip(&line_spans).enumerate() {
let line_num = lc.line + i + 1;
let connector = if multiline {
match (first_line, last_line) {
(Some(first), Some(_last)) => {
if i == first {
"/"
} else {
"|"
}
}
_ => " ",
}
} else {
" "
};
print_code_line(
f, line_num, line_text, connector, start, end, multiline, first_line, last_line, &label,
)?;
}
if multiline && let (Some(_), Some(last)) = (first_line, last_line) {
let start_col = line_spans[last].0;
let end_col = line_spans[last].1;
print_multiline_underline(f, start_col, end_col, &label)?;
}
prev_last_line = Some(lc.line + lc.contents.lines().count());
}
}
}
if let Some(help) = &self.backtrace.help {
writeln!(
f,
"{INDENT } |\n\
{INDENT } = {help}",
)?;
}
let leo_backtrace = std::env::var("LEO_BACKTRACE").unwrap_or_default().trim().to_owned();
match leo_backtrace.as_ref() {
"1" => {
let mut printer = BacktracePrinter::default();
printer = printer.verbosity(Verbosity::Medium);
printer = printer.lib_verbosity(Verbosity::Medium);
let trace = printer.format_trace_to_string(&self.backtrace.backtrace).map_err(|_| fmt::Error)?;
write!(f, "\n{trace}")?;
}
"full" => {
let mut printer = BacktracePrinter::default();
printer = printer.verbosity(Verbosity::Full);
printer = printer.lib_verbosity(Verbosity::Full);
let trace = printer.format_trace_to_string(&self.backtrace.backtrace).map_err(|_| fmt::Error)?;
write!(f, "\n{trace}")?;
}
_ => {}
}
Ok(())
}
}
impl std::error::Error for Formatted {
fn description(&self) -> &str {
&self.backtrace.message
}
}