use ansi_term::{Color, Style};
use std::{fmt, io};
use crate::{reporter::Message, Loc, Reporter};
pub struct SourceInfo<'a> {
name: Option<&'a str>,
text: &'a str,
newlines: Vec<usize>,
}
#[derive(Clone, Copy)]
pub enum DisplayOptions {
Terminal,
String,
}
struct Repeated(usize, char);
impl fmt::Display for Repeated {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for _ in 0..self.0 {
write!(f, "{}", self.1)?
}
Ok(())
}
}
impl<'a> SourceInfo<'a> {
pub fn new(name: Option<&'a str>, text: &'a str) -> Self {
let mut newlines = Vec::new();
for (i, c) in text.char_indices() {
if c == '\n' {
newlines.push(i)
}
}
Self {
name,
text,
newlines,
}
}
pub fn name(&self) -> &str {
match &self.name {
Some(s) => s,
None => "<none>",
}
}
fn line_idx(&self, bytepos: usize) -> usize {
self.newlines.partition_point(|i| *i < bytepos)
}
fn line_start(&self, n: usize) -> usize {
if n == 0 {
0
} else if n > self.newlines.len() {
self.text.len() - 1
} else {
(self.text.len() - 1).min(self.newlines[n - 1] + 1)
}
}
fn line_end(&self, n: usize) -> usize {
if n < self.newlines.len() {
self.newlines[n]
} else {
self.text.len()
}
}
pub fn show_source<W: fmt::Write>(
&self,
loc: Loc,
w: &mut W,
config: DisplayOptions,
) -> fmt::Result {
let (start_line, end_line) = (self.line_idx(loc.start), self.line_idx(loc.end));
let start_char = &self.text[self.line_start(start_line)..loc.start]
.chars()
.count();
writeln!(
w,
"--> {}:{}:{}",
self.name(),
start_line + 1,
start_char + 1
)?;
let style = Style::new().bold().underline().fg(Color::Red);
for line in start_line..=end_line {
let (s, e) = (self.line_start(line), self.line_end(line));
let (hs, he) = (s.max(loc.start), e.min(loc.end));
match config {
DisplayOptions::String => {
writeln!(w, "{:4>}| {}", line + 1, &self.text[s..e],)?;
writeln!(
w,
"{:4>}| {}{}",
line + 1,
Repeated(hs - s, ' '),
Repeated(he - hs, '^')
)?;
}
DisplayOptions::Terminal => {
writeln!(
w,
"{:4>}| {}{}{}",
line + 1,
&self.text[s..hs],
style.paint(&self.text[hs..he]),
&self.text[he..e]
)?;
}
}
}
Ok(())
}
pub fn write_fmt(
&self,
w: &mut impl fmt::Write,
m: &Message,
options: DisplayOptions,
) -> fmt::Result {
match m {
Message::Error(e) => {
writeln!(w, "error[{}]: {}", e.code.short, e.message)?;
if let Some(loc) = e.loc {
self.show_source(loc, w, options)?;
}
}
Message::Info(m) => {
writeln!(w, "info: {m}")?;
}
}
Ok(())
}
pub fn extract_report_to(
&self,
w: &mut impl fmt::Write,
r: Reporter,
options: DisplayOptions,
) -> fmt::Result {
for m in r.poll().into_iter() {
self.write_fmt(w, &m, options)?;
}
Ok(())
}
pub fn extract_report_to_io(
&self,
w: &mut impl io::Write,
r: Reporter,
options: DisplayOptions,
) -> io::Result<()> {
let mut buf = String::new();
for m in r.poll().into_iter() {
self.write_fmt(&mut buf, &m, options)
.expect("failed to format message {m}");
w.write(buf.as_bytes())?;
buf.truncate(0);
}
w.flush()?;
Ok(())
}
pub fn extract_report_to_string(&self, r: Reporter) -> String {
let mut out = String::new();
self.extract_report_to(&mut out, r, DisplayOptions::String)
.unwrap();
out
}
}