use std::{
fmt::{ Display, Debug, Formatter, Result as FMTResult, },
cell::{ UnsafeCell, },
};
use crate::{
ansi,
util::{ padding, count_digits, some, },
source::{ SOURCE_MANAGER, SourceRegion, },
};
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[allow(missing_docs)]
pub enum MessageKind {
Error,
Warning,
Notice,
}
impl MessageKind {
pub fn get_ansi (self) -> ansi::Foreground {
use MessageKind::*;
match self {
Error => ansi::Foreground::Red,
Warning => ansi::Foreground::Yellow,
Notice => ansi::Foreground::Green,
}
}
pub fn get_name (self) -> &'static str {
use MessageKind::*;
match self {
Error => "Error",
Warning => "Warning",
Notice => "Notice",
}
}
pub fn get_whitespace (self, offset: usize) -> &'static str {
padding((self.get_name().len() - offset) as _)
}
}
impl Display for MessageKind {
fn fmt (&self, f: &mut Formatter) -> FMTResult {
write!(
f, "{}{}{}",
self.get_ansi(),
self.get_name(),
ansi::Foreground::Reset
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Message {
pub origin: Option<SourceRegion>,
pub kind: MessageKind,
pub content: String,
}
impl Message {
pub fn new (origin: Option<SourceRegion>, kind: MessageKind, content: String) -> Self {
Self {
origin,
kind,
content,
}
}
#[allow(clippy::cognitive_complexity)] pub fn excerpt (&self, f: &mut Formatter) -> FMTResult {
let origin = some!(self.origin; Ok(()));
let source = SOURCE_MANAGER.get(some!(origin.source; Ok(()))).expect("Internal error, invalid SourceKey");
let chars = source.chars();
let mut start_index = origin.start.index.min(chars.len() - 1);
if start_index != 0 {
if matches!(chars[start_index], '\r'|'\n') {
start_index += 1;
} else {
while start_index > 0 {
if !matches!(chars[start_index - 1], '\r'|'\n') {
start_index -= 1;
} else {
break
}
}
}
}
let mut end_index = origin.end.index.min(chars.len());
if end_index != chars.len() {
if matches!(chars[end_index], '\r'|'\n') {
end_index -= 1;
} else {
while end_index < chars.len() - 1 {
if !matches!(chars[end_index + 1], '\r'|'\n') {
end_index += 1;
} else {
break
}
}
}
}
let slice = &chars[start_index..(end_index + 1).min(chars.len())];
let mut num_lines = 1usize;
for ch in slice.iter() {
if *ch == '\n' { num_lines += 1 }
}
if num_lines == 1 {
let line_num = origin.start.line + 1;
let line_num_digits = count_digits(line_num as _, 10);
write!(f, "{}│\n│{} {} {}│{} ", self.kind.get_ansi(), ansi::Foreground::Cyan, line_num, self.kind.get_ansi(), ansi::Foreground::BrightBlack)?;
for (i, ch) in slice.iter().enumerate() {
if i == origin.start.column as _ { write!(f, "{}", ansi::Foreground::Reset)?; }
else if i == origin.end.column as _ { write!(f, "{}", ansi::Foreground::BrightBlack)?; }
write!(f, "{}", ch)?;
}
write!(f, "\n{}{}└──", padding((line_num_digits + 3) as _), self.kind.get_ansi())?;
for i in 0..slice.len() as _ {
if i < origin.start.column { write!(f, "─")?; }
else if i < origin.end.column { write!(f, "^")?; }
else { break }
}
writeln!(f, "{}", ansi::Foreground::Reset)?;
} else {
let last_line_num = origin.end.line + 1;
let last_line_num_digits = count_digits(last_line_num as _, 10);
let gap_pad = padding((last_line_num_digits + 2) as _);
write!(f, "{}│\n│{}┌", self.kind.get_ansi(), gap_pad,)?;
for _ in 0..origin.start.column + 2 {
write!(f, "─")?;
}
let first_line_num = origin.start.line + 1;
let first_line_num_digits = count_digits(first_line_num as _, 10);
write!(f, "v\n│ {}{}{}{} │{} ", padding((last_line_num_digits - first_line_num_digits) as _), ansi::Foreground::Cyan, first_line_num, self.kind.get_ansi(), ansi::Foreground::BrightBlack)?;
let mut line_num = first_line_num;
let mut column = 0;
for ch in slice.iter() {
if *ch != '\n' {
if line_num - 1 == origin.start.line && column == origin.start.column as _ { write!(f, "{}", ansi::Foreground::Reset)?; }
else if line_num - 1 == origin.end.line && column == origin.end.column as _ { write!(f, "{}", ansi::Foreground::BrightBlack)?; }
write!(f, "{}", ch)?;
column += 1;
} else {
column = 0;
line_num += 1;
let line_num_digits = count_digits(line_num as _, 10);
write!(f, "\n{}│ {}{}{}{} │ ", self.kind.get_ansi(), padding((last_line_num_digits - line_num_digits) as _), ansi::Foreground::Cyan, line_num, self.kind.get_ansi())?;
if line_num > first_line_num { write!(f, "{}", ansi::Foreground::Reset)?; }
}
}
write!(f, "\n {}{}â””", gap_pad, self.kind.get_ansi())?;
for _ in 0..origin.end.column + 1 {
write!(f, "─")?;
}
writeln!(f, "^{}", ansi::Foreground::Reset)?;
}
Ok(())
}
}
impl Display for Message {
fn fmt (&self, f: &mut Formatter) -> FMTResult {
writeln!(f, "\n{}: {}", self.kind, self.content)?;
if let Some(origin) = self.origin {
writeln!(f, "{}│{} {}at: {}{}{}",
self.kind.get_ansi(),
ansi::Foreground::Reset,
self.kind.get_whitespace(4),
ansi::Foreground::Cyan,
origin,
ansi::Foreground::Reset
)?;
}
self.excerpt(f)
}
}
pub struct Session (UnsafeCell<Option<Vec<Message>>>);
unsafe impl Send for Session { }
unsafe impl Sync for Session { }
pub static SESSION: Session = Session(UnsafeCell::new(None));
impl Session {
#[allow(clippy::mut_from_ref)]
unsafe fn inner (&self) -> &mut Option<Vec<Message>> {
&mut *self.0.get()
}
#[allow(clippy::mut_from_ref)]
fn vec (&self) -> &mut Vec<Message> {
let inner = unsafe { self.inner() };
inner.as_mut().expect("Internal error: Session not initialized")
}
pub fn messages (&self) -> &[Message] {
self.vec().as_slice()
}
pub fn init (&self) {
let inner = unsafe { self.inner() };
assert!(inner.is_none(), "Internal error: Session double initialized");
inner.replace(Vec::default());
}
pub fn message (&self, origin: Option<SourceRegion>, kind: MessageKind, content: String) {
let msg = Message::new(
origin,
kind,
content
);
#[cfg(feature = "backtrace")] {
let bt = backtrace::Backtrace::new();
println!("New session message caught:\n{}\nat {:?}", &msg, bt);
}
self.vec().push(msg)
}
pub fn error (&self, origin: Option<SourceRegion>, content: String) {
self.vec().push(Message::new(
origin,
MessageKind::Error,
content
))
}
pub fn warning (&self, origin: Option<SourceRegion>, content: String) {
self.vec().push(Message::new(
origin,
MessageKind::Warning,
content
))
}
pub fn notice (&self, origin: Option<SourceRegion>, content: String) {
self.vec().push(Message::new(
origin,
MessageKind::Notice,
content
))
}
pub fn print_messages (&self) {
for message in self.vec().iter() {
print!("{}", message)
}
}
pub fn print_errors (&self) {
for message in self.vec().iter() {
if message.kind == MessageKind::Error { print!("{}", message) }
}
}
pub fn print_warnings (&self) {
for message in self.vec().iter() {
if message.kind == MessageKind::Warning { print!("{}", message) }
}
}
pub fn print_notices (&self) {
for message in self.vec().iter() {
if message.kind == MessageKind::Notice { print!("{}", message) }
}
}
}