use crate::Span;
use colored::{ColoredString, Colorize};
pub trait DiagnosticColorer {
fn title(&self) -> &'static str;
fn primary_color(&self, token: &'_ str) -> ColoredString;
}
pub(crate) fn pretty_print(
f: &mut dyn std::io::Write,
file_name: &str,
text: &str,
span: Span,
description: &str,
colorer: &'static dyn DiagnosticColorer,
) -> std::io::Result<()> {
let start_line_number = text[..span.start].matches('\n').count();
let end_line_number = text[..span.end].matches('\n').count();
let file_lines = text.split('\n').collect::<Vec<&str>>();
let chars_in_line_before: usize = file_lines[..start_line_number].iter().map(|l| l.len()).sum();
let chars_in_line_before = chars_in_line_before + start_line_number;
let line = &file_lines[start_line_number];
let start_in_line = span.start - chars_in_line_before;
let end_in_line = std::cmp::min(start_in_line + (span.end - span.start), line.len());
let prefix = &line[..start_in_line];
let offending = colorer.primary_color(&line[start_in_line..end_in_line]).bold();
let suffix = &line[end_in_line..];
let arrow = "-->".bright_blue().bold();
let file_path = format!("{}:{}", file_name, start_line_number + 1).underline();
writeln!(
f,
"{}: {}",
colorer.primary_color(colorer.title()).bold(),
description.bold()
)?;
writeln!(f, " {arrow} {file_path}")?;
writeln!(f, "{}", format_line_number(0))?;
writeln!(f, "{}", format_line_number_with_line(start_line_number, &file_lines))?;
writeln!(
f,
"{}{}{}{}",
format_line_number(start_line_number + 1),
prefix,
offending,
suffix
)?;
if offending.len() == 0 {
let spacing = " ".repeat(start_in_line);
writeln!(
f,
"{}{}{}",
format_line_number(0),
spacing,
colorer.primary_color("^ Unexpected token.").bold()
)?;
}
for line_number in start_line_number + 2..end_line_number + 2 {
writeln!(f, "{}", format_line_number_with_line(line_number, &file_lines))?;
}
writeln!(f, "{}", format_line_number(0))
}
fn format_line_number_with_line(line_number: usize, lines: &[&str]) -> colored::ColoredString {
if line_number > 0 && line_number <= lines.len() {
colored::ColoredString::from(format!("{}{}", format_line_number(line_number), lines[line_number - 1]).as_str())
} else {
format_line_number(line_number)
}
}
fn format_line_number(line_number: usize) -> colored::ColoredString {
if line_number > 0 {
format!("{line_number:2} | ").bold().bright_blue()
} else {
" | ".bold().bright_blue()
}
}