use std::{borrow::Cow, env};
use crate::{
render::{RenderManager, provider::SourceProvider},
source::Span,
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct InternalError {
pub(crate) message: Cow<'static, str>,
pub(crate) source: Option<Span>,
}
impl InternalError {
pub(crate) fn new(message: impl Into<Cow<'static, str>>, source: impl Into<Span>) -> Self {
Self {
message: message.into(),
source: Some(source.into()),
}
}
pub(crate) fn with_source_id(self, id: usize) -> Error {
Error {
message: self.message,
source: self.source.map(|sp| (id, sp)),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Error {
pub message: Cow<'static, str>,
pub source: Option<(usize, Span)>,
}
impl Error {
pub(crate) fn new_general(message: impl Into<Cow<'static, str>>) -> Self {
Self {
message: message.into(),
source: None,
}
}
}
const FMT_RESET: &str = "\x1b[0m";
const FMT_GUTTER: &str = "\x1b[97m";
const FMT_ITALICS: &str = "\x1b[3m";
const FMT_DIM: &str = "\x1b[2m";
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "\x1b[1;31merror:{FMT_RESET} {}", self.message)
}
}
impl std::error::Error for Error {}
impl<P: SourceProvider> RenderManager<'_, P> {
pub fn display_error(&self, error: Error) {
let fmt_hl = if env::var("TERM")
.is_ok_and(|v| ["xterm-kitty", "alacritty", "foot", "footclient"].contains(&v.as_str()))
{
"\x1b[58;5;1;4:3m"
} else {
"\x1b[1m"
};
fn digits(x: usize) -> usize {
x.ilog10() as usize + 1
}
eprintln!("\x1b[1;31merror:{FMT_RESET} {}", error.message);
let Some((src_id, span)) = error.source else {
return;
};
let (key, source) = &self.sources[src_id];
let span = Span {
start: span.start.min(source.len()),
end: span.end.min(source.len() + 1),
};
let mut lines = Vec::new();
let mut ch = 0;
for line in source.lines() {
lines.push(Span {
start: ch,
end: ch + line.len(),
});
ch += line.len() + 1;
}
let index_to_line_span = |idx: usize| -> (usize, usize, Span) {
for (ln, span @ Span { start, end }) in lines.iter().enumerate() {
if idx <= *end {
return (ln + 1, idx - start + 1, *span);
}
}
return lines.last().map_or_else(
|| (lines.len() + 1, 1, Span::char(source.len() - 1)),
|span @ Span { start, end: _ }| (lines.len(), idx - start + 1, *span),
);
};
let (ln_start, ch_start, first_line) = index_to_line_span(span.start);
let (ln_end, _, last_line) = index_to_line_span(span.end);
let ln_digits = digits(ln_end);
let pad_start = " ".repeat(ln_digits);
eprintln!(
" {}{FMT_GUTTER}--> {FMT_ITALICS}{}:{ln_start}:{ch_start}{FMT_RESET}",
pad_start,
P::display_key(key),
);
eprintln!("{FMT_GUTTER} {} |", pad_start);
if (span.end >= source.len()
|| (span.start == first_line.end && span.end == last_line.start))
&& (ln_start + 1 == ln_end || ln_start == ln_end)
{
eprintln!(
" {}{ln_start} | {FMT_RESET}{}{fmt_hl} {FMT_RESET}",
&pad_start[0..ln_digits - digits(ln_start)],
&source[first_line.start..first_line.end]
);
} else if ln_start != ln_end {
eprintln!(
" {}{ln_start} | {FMT_RESET}{FMT_DIM}{}{FMT_RESET}{fmt_hl}{}{FMT_RESET}",
&pad_start[0..ln_digits - digits(ln_start)],
&source[first_line.start..span.start],
&source[span.start..first_line.end],
);
for ln in ln_start + 1..ln_end {
let Span { start, end } = lines[ln - 1];
eprintln!(
" {}{FMT_GUTTER}{ln} | {FMT_RESET}{fmt_hl}{}{FMT_RESET}",
&pad_start[0..ln_digits - digits(ln)],
&source[start..end],
);
}
eprintln!(
" {FMT_GUTTER}{ln_end} | {FMT_RESET}{fmt_hl}{}{FMT_RESET}{FMT_DIM}{}{FMT_RESET}",
&source[last_line.start..span.end],
&source[span.end..last_line.end],
);
} else {
eprintln!(
" {ln_end} | {FMT_RESET}{FMT_DIM}{}{FMT_RESET}{fmt_hl}{}{FMT_RESET}{FMT_DIM}{}{FMT_RESET}",
&source[first_line.start..span.start],
&source[span.start..span.end],
&source[span.end.min(source.len())..first_line.end],
);
}
eprintln!(" {}{FMT_GUTTER} |{FMT_RESET}", pad_start);
}
}