#![doc = include_str!("../readme.md")]
#![warn(missing_docs)]
mod display;
mod draw;
mod source;
mod write;
pub use crate::{
draw::{ColorGenerator, Fmt},
source::{FileCache, Line, Source},
};
use std::fmt::{Debug, Display, Formatter};
pub use yansi::Color;
use crate::display::*;
use std::{
cmp::{Eq, PartialEq},
hash::Hash,
io::Write,
ops::Range,
};
use unicode_width::UnicodeWidthChar;
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct FileID {
hash: u64,
}
impl Display for FileID {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "FileID({})", self.hash)
}
}
impl FileID {
pub unsafe fn new(id: u64) -> Self {
Self { hash: id }
}
pub fn with_range(self, range: Range<usize>) -> FileSpan {
FileSpan { start: range.start, end: range.end, file: self }
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct FileSpan {
start: usize,
end: usize,
file: FileID,
}
impl Debug for FileID {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FileID").field("id", &self.hash).finish()
}
}
impl Debug for FileSpan {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FileSpan").field("start", &self.start).field("end", &self.end).field("file", &self.file).finish()
}
}
impl FileSpan {
pub unsafe fn new(start: usize, end: usize, file: FileID) -> Self {
Self { start, end, file }
}
pub fn get_range(&self) -> Range<usize> {
self.start..self.end
}
pub fn set_range(&mut self, range: Range<usize>) {
self.start = range.start;
self.end = range.end;
}
pub fn with_range(self, range: Range<usize>) -> Self {
Self { start: range.start, end: range.end, ..self }
}
pub fn get_file(&self) -> FileID {
self.file
}
pub fn set_file(&mut self, file: FileID) {
self.file = file;
}
pub fn with_file(self, file: FileID) -> Self {
Self { file, ..self }
}
}
pub trait Span {
type SourceId: PartialEq + ToOwned + ?Sized;
fn source(&self) -> &Self::SourceId;
fn start(&self) -> usize;
fn end(&self) -> usize;
fn len(&self) -> usize {
self.end().saturating_sub(self.start())
}
fn contains(&self, offset: usize) -> bool {
(self.start()..self.end()).contains(&offset)
}
}
impl Span for FileSpan {
type SourceId = FileID;
fn source(&self) -> &Self::SourceId {
&self.file
}
fn start(&self) -> usize {
self.start
}
fn end(&self) -> usize {
self.end
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Label {
span: FileSpan,
msg: Option<String>,
color: Option<Color>,
order: i32,
priority: i32,
}
impl Label {
pub fn new(span: FileSpan) -> 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 Report {
kind: Box<dyn ReportLevel>,
code: Option<usize>,
message: String,
note: Option<String>,
help: Option<String>,
location: (FileID, usize),
labels: Vec<Label>,
config: Config,
}
impl Report {
pub fn new<R>(kind: R, src_id: FileID, offset: usize) -> ReportBuilder
where
R: ReportLevel + 'static,
{
ReportBuilder {
kind: Box::new(kind),
code: None,
message: String::new(),
note: None,
help: None,
location: (src_id.into(), offset),
labels: Vec::new(),
config: Config::default(),
}
}
pub fn eprint(&self, cache: FileCache) -> std::io::Result<()> {
self.write(cache, std::io::stderr().lock())
}
pub fn print(&self, cache: FileCache) -> std::io::Result<()> {
self.write_for_stdout(cache, std::io::stdout().lock())
}
}
impl Debug for Report {
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,
}
pub struct ReportBuilder {
kind: Box<dyn ReportLevel>,
code: Option<usize>,
message: String,
note: Option<String>,
help: Option<String>,
location: (FileID, usize),
labels: Vec<Label>,
config: Config,
}
impl ReportBuilder {
pub fn set_code(&mut self, code: Option<usize>) {
self.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.message = message.to_string();
}
pub fn with_message<M: ToString>(mut self, message: M) -> Self {
self.message = message.to_string();
self
}
pub fn set_note<N: ToString>(&mut self, note: N) {
self.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.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.config; self.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.config = config;
self
}
pub fn finish(self) -> Report {
Report {
kind: self.kind,
code: self.code,
message: self.message,
note: self.note,
help: self.help,
location: self.location,
labels: self.labels,
config: self.config,
}
}
}
impl Debug for ReportBuilder {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ReportBuilder")
.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()
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum LabelAttach {
Start,
Middle,
End,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum CharSet {
Unicode,
Ascii,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Config {
cross_gap: bool,
label_attach: LabelAttach,
compact: bool,
underlines: bool,
multiline_arrows: bool,
color: bool,
tab_width: usize,
char_set: CharSet,
}
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 = color;
self
}
pub fn with_tab_width(mut self, tab_width: usize) -> Self {
self.tab_width = tab_width;
self
}
pub fn with_char_set(mut self, char_set: CharSet) -> Self {
self.char_set = char_set;
self
}
fn margin_color(&self) -> Option<Color> {
Some(Color::Fixed(246)).filter(|_| self.color)
}
fn skipped_margin_color(&self) -> Option<Color> {
Some(Color::Fixed(240)).filter(|_| self.color)
}
fn unimportant_color(&self) -> Option<Color> {
Some(Color::Fixed(249)).filter(|_| self.color)
}
fn note_color(&self) -> Option<Color> {
Some(Color::Fixed(115)).filter(|_| self.color)
}
fn filter_color(&self, color: Option<Color>) -> Option<Color> {
color.filter(|_| self.color)
}
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: true,
tab_width: 4,
char_set: CharSet::Unicode,
}
}
}