#![doc = include_str!("../readme.md")]
#![warn(missing_docs)]
mod display;
mod draw;
mod style;
mod write;
mod characters;
mod windows;
use crate::{characters::Draw, display::*};
pub use crate::{
characters::{BuiltinDrawer, DrawElements},
draw::{Console, Palette},
style::{color::Color, paint::Paint, style::Style},
windows::enable_ansi_color,
};
use core::{
cmp::{Eq, PartialEq},
fmt::{Debug, Display, Formatter},
hash::Hash,
};
pub use source_cache::{SourceCache, SourceID, SourceSpan};
use std::io::Write;
use unicode_width::UnicodeWidthChar;
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Label {
span: SourceSpan,
msg: Option<String>,
color: Option<Color>,
order: i32,
priority: i32,
}
impl Label {
pub fn new(span: SourceSpan) -> Self {
Self { span, msg: None, color: None, order: 0, priority: 0 }
}
pub fn with_message<M: ToString>(mut self, msg: M) -> Self {
self.msg = Some(msg.to_string());
self
}
pub fn with_color(mut self, color: Color) -> Self {
self.color = Some(color);
self
}
pub fn with_order(mut self, order: i32) -> Self {
self.order = order;
self
}
pub fn with_priority(mut self, priority: i32) -> Self {
self.priority = priority;
self
}
}
pub struct Diagnostic {
kind: Box<dyn ReportLevel>,
code: Option<usize>,
message: String,
note: Option<String>,
help: Option<String>,
file: SourceID,
location: Option<u32>,
labels: Vec<Label>,
config: Config,
}
pub struct DiagnosticBuilder {
inner: Diagnostic,
}
impl Diagnostic {
pub fn new<R>(kind: R) -> DiagnosticBuilder
where
R: ReportLevel + 'static,
{
DiagnosticBuilder {
inner: Diagnostic {
kind: Box::new(kind),
code: None,
message: "".to_string(),
note: None,
help: None,
file: Default::default(),
location: None,
labels: vec![],
config: Default::default(),
},
}
}
}
impl Diagnostic {
pub fn eprint(&self, cache: &SourceCache) -> std::io::Result<()> {
self.write(cache, std::io::stderr().lock())
}
pub fn print(&self, cache: &SourceCache) -> std::io::Result<()> {
self.write_for_stdout(cache, std::io::stdout().lock())
}
}
impl Debug for Diagnostic {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Report")
.field("kind", &self.kind)
.field("code", &self.code)
.field("msg", &self.message)
.field("note", &self.note)
.field("help", &self.help)
.field("config", &self.config)
.finish()
}
}
pub trait ReportLevel: Debug {
fn level(&self) -> u8;
fn get_color(&self) -> Color;
}
impl Debug for ReportKind {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
match self {
ReportKind::Error => f.write_str("ERROR"),
ReportKind::Alert => f.write_str("ALERT"),
ReportKind::Trace => f.write_str("TRACE"),
ReportKind::Blame => f.write_str("BLAME"),
ReportKind::Fatal => f.write_str("FATAL"),
}
}
}
impl ReportLevel for ReportKind {
fn level(&self) -> u8 {
match self {
ReportKind::Trace => 0,
ReportKind::Blame => 150,
ReportKind::Alert => 200,
ReportKind::Error => 250,
ReportKind::Fatal => 255,
}
}
fn get_color(&self) -> Color {
match self {
ReportKind::Trace => Color::Cyan,
ReportKind::Blame => Color::Green,
ReportKind::Alert => Color::Yellow,
ReportKind::Error => Color::Red,
ReportKind::Fatal => Color::Magenta,
}
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum ReportKind {
Trace,
Blame,
Alert,
Error,
Fatal,
}
impl DiagnosticBuilder {
pub fn set_code(&mut self, code: Option<usize>) {
self.inner.code = code;
}
pub fn with_code(mut self, code: usize) -> Self {
self.set_code(Some(code));
self
}
pub fn set_message<M: ToString>(&mut self, message: M) {
self.inner.message = message.to_string();
}
pub fn with_message<M: ToString>(mut self, message: M) -> Self {
self.inner.message = message.to_string();
self
}
pub fn set_location(&mut self, file: SourceID, start: Option<u32>) {
self.inner.file = file;
self.inner.location = start;
}
pub fn with_location(mut self, file: SourceID, start: Option<u32>) -> Self {
self.set_location(file, start);
self
}
pub fn set_note<N: ToString>(&mut self, note: N) {
self.inner.note = Some(note.to_string());
}
pub fn with_note<N: ToString>(mut self, note: N) -> Self {
self.set_note(note);
self
}
pub fn set_help<N: ToString>(&mut self, note: N) {
self.inner.help = Some(note.to_string());
}
pub fn with_help<N: ToString>(mut self, note: N) -> Self {
self.set_help(note);
self
}
pub fn add_label(&mut self, label: Label) {
self.add_labels(std::iter::once(label));
}
pub fn add_labels<L: IntoIterator<Item = Label>>(&mut self, labels: L) {
let config = &self.inner.config; self.inner.labels.extend(labels.into_iter().map(|mut label| {
label.color = config.filter_color(label.color);
label
}));
}
pub fn with_label(mut self, label: Label) -> Self {
self.add_label(label);
self
}
pub fn with_labels<L: IntoIterator<Item = Label>>(mut self, labels: L) -> Self {
self.add_labels(labels);
self
}
pub fn with_config(mut self, config: Config) -> Self {
self.inner.config = config;
self
}
pub fn finish(self) -> Diagnostic {
self.inner
}
}
impl Debug for DiagnosticBuilder {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Debug::fmt(&self.inner, f)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum LabelAttach {
Start,
Middle,
End,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Config {
cross_gap: bool,
label_attach: LabelAttach,
compact: bool,
underlines: bool,
multiline_arrows: bool,
pub color_enable: bool,
pub margin_color: Option<Color>,
pub margin_skip_color: Option<Color>,
pub unimportant_color: Option<Color>,
tab_width: usize,
pub characters: DrawElements,
}
impl Config {
pub fn with_cross_gap(mut self, cross_gap: bool) -> Self {
self.cross_gap = cross_gap;
self
}
pub fn with_label_attach(mut self, label_attach: LabelAttach) -> Self {
self.label_attach = label_attach;
self
}
pub fn with_compact(mut self, compact: bool) -> Self {
self.compact = compact;
self
}
pub fn with_underlines(mut self, underlines: bool) -> Self {
self.underlines = underlines;
self
}
pub fn with_multiline_arrows(mut self, multiline_arrows: bool) -> Self {
self.multiline_arrows = multiline_arrows;
self
}
pub fn with_color(mut self, color: bool) -> Self {
self.color_enable = color;
self
}
pub fn with_tab_width(mut self, tab_width: usize) -> Self {
self.tab_width = tab_width;
self
}
pub fn with_characters(mut self, set: impl Draw) -> Self {
self.characters = set.get_elements();
self
}
fn margin_color(&self) -> Option<Color> {
Some(match self.margin_color {
None => Color::Fixed(27),
Some(s) => s,
})
.filter(|_| self.color_enable)
}
fn skipped_margin_color(&self) -> Option<Color> {
Some(match self.margin_skip_color {
None => Color::Fixed(27),
Some(s) => s,
})
.filter(|_| self.color_enable)
}
fn unimportant_color(&self) -> Option<Color> {
Some(match self.unimportant_color {
None => Color::Fixed(249),
Some(s) => s,
})
.filter(|_| self.color_enable)
}
fn note_color(&self) -> Option<Color> {
Some(Color::Fixed(115)).filter(|_| self.color_enable)
}
fn filter_color(&self, color: Option<Color>) -> Option<Color> {
color.filter(|_| self.color_enable)
}
fn char_width(&self, c: char, col: usize) -> (char, usize) {
match c {
'\t' => {
let tab_end = (col / self.tab_width + 1) * self.tab_width;
(' ', tab_end - col)
}
c if c.is_whitespace() => (' ', 1),
_ => (c, c.width().unwrap_or(1)),
}
}
}
impl Default for Config {
fn default() -> Self {
Self {
cross_gap: true,
label_attach: LabelAttach::Middle,
compact: false,
underlines: true,
multiline_arrows: true,
color_enable: true,
margin_color: None,
margin_skip_color: None,
unimportant_color: None,
tab_width: 4,
characters: BuiltinDrawer::Unicode.get_elements(),
}
}
}