refactor: a little bit of refactor in order to make it easier to add new types of error messages

This commit is contained in:
Sofia R 2023-04-29 22:05:48 -03:00
parent 7202321c72
commit b20c8eac68
11 changed files with 730 additions and 578 deletions

30
Cargo.lock generated
View File

@ -500,9 +500,9 @@ checksum = "809e18805660d7b6b2e2b9f316a5099521b5998d5cba4dda11b5157a21aaef03"
[[package]]
name = "hvm"
version = "1.0.3"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f1fc0af0e1b783f072678cd4b557bee147e5c8653930778f5c0c333e6ac0849"
checksum = "35672d6ee046e8ebd6373a48aad5b926b4920cee27cb4f45e74643e7388c7a9e"
dependencies = [
"HOPA",
"backtrace",
@ -631,7 +631,7 @@ dependencies = [
[[package]]
name = "kind-checker"
version = "0.1.1"
version = "0.1.2"
dependencies = [
"fxhash",
"hvm",
@ -643,7 +643,7 @@ dependencies = [
[[package]]
name = "kind-derive"
version = "0.1.1"
version = "0.1.2"
dependencies = [
"fxhash",
"im-rc",
@ -654,7 +654,7 @@ dependencies = [
[[package]]
name = "kind-driver"
version = "0.1.1"
version = "0.1.2"
dependencies = [
"anyhow",
"dashmap",
@ -673,7 +673,7 @@ dependencies = [
[[package]]
name = "kind-parser"
version = "0.1.1"
version = "0.1.2"
dependencies = [
"fxhash",
"kind-report",
@ -683,7 +683,7 @@ dependencies = [
[[package]]
name = "kind-pass"
version = "0.1.1"
version = "0.1.2"
dependencies = [
"anyhow",
"fxhash",
@ -697,7 +697,7 @@ dependencies = [
[[package]]
name = "kind-query"
version = "0.1.1"
version = "0.1.2"
dependencies = [
"fxhash",
"kind-checker",
@ -712,7 +712,7 @@ dependencies = [
[[package]]
name = "kind-report"
version = "0.1.1"
version = "0.1.2"
dependencies = [
"fxhash",
"kind-span",
@ -724,11 +724,11 @@ dependencies = [
[[package]]
name = "kind-span"
version = "0.1.1"
version = "0.1.2"
[[package]]
name = "kind-target-hvm"
version = "0.1.1"
version = "0.1.2"
dependencies = [
"hvm",
"kind-derive",
@ -739,7 +739,7 @@ dependencies = [
[[package]]
name = "kind-target-kdl"
version = "0.1.1"
version = "0.1.2"
dependencies = [
"fxhash",
"im-rc",
@ -754,7 +754,7 @@ dependencies = [
[[package]]
name = "kind-tests"
version = "0.1.1"
version = "0.1.2"
dependencies = [
"kind-checker",
"kind-driver",
@ -772,7 +772,7 @@ dependencies = [
[[package]]
name = "kind-tree"
version = "0.1.1"
version = "0.1.2"
dependencies = [
"fxhash",
"hvm",
@ -782,7 +782,7 @@ dependencies = [
[[package]]
name = "kind2"
version = "0.3.7"
version = "0.3.9"
dependencies = [
"anyhow",
"clap 4.0.29",

View File

@ -6,11 +6,11 @@ use clap::{Parser, Subcommand};
use driver::resolution::ResolutionError;
use kind_driver::session::Session;
use kind_report::data::{Diagnostic, Log, Severity};
use kind_report::report::{FileCache, Report};
use kind_report::data::{Diagnostic, Log, Severity, FileCache};
use kind_report::RenderConfig;
use kind_driver as driver;
use kind_report::report::mode::{Renderable, Classic};
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
@ -122,16 +122,16 @@ where
}
}
pub fn render_to_stderr<T, E>(render_config: &RenderConfig, session: &T, err: &E)
pub fn render_to_stderr<C, T, E>(render_config: &RenderConfig, session: &T, err: &E)
where
T: FileCache,
E: Report,
E: Renderable<C>,
{
Report::render(
E::render(
err,
&mut ToWriteFmt(std::io::stderr()),
session,
render_config,
&mut ToWriteFmt(std::io::stderr()),
)
.unwrap();
}
@ -168,7 +168,7 @@ pub fn compile_in_session<T>(
contains_error = true;
}
render_to_stderr(render_config, &session, &diagnostic)
render_to_stderr::<Classic, _, _>(render_config, &session, &diagnostic)
}
if !contains_error {

View File

@ -1,7 +1,7 @@
use checker::eval;
use diagnostic::{DriverDiagnostic, GenericDriverError};
use kind_pass::{desugar, erasure, inline::inline_book};
use kind_report::report::FileCache;
use kind_report::data::FileCache;
use kind_span::SyntaxCtxIndex;
use hvm::language::{syntax as backend};

View File

@ -1,4 +1,4 @@
use std::time::Duration;
use std::{time::Duration, path::PathBuf, ops::Sub};
use kind_span::{Range, SyntaxCtxIndex};
@ -52,6 +52,18 @@ pub struct DiagnosticFrame {
pub hints: Vec<String>,
pub positions: Vec<Marker>,
}
pub struct Hints<'a>(pub &'a Vec<String>);
pub struct Subtitles<'a>(pub &'a Vec<Subtitle>);
pub struct Markers<'a>(pub &'a Vec<Marker>);
pub struct Header<'a> {
pub severity: &'a Severity,
pub title: &'a String
}
pub enum Log {
Checking(String),
Checked(Duration),
@ -64,3 +76,28 @@ pub trait Diagnostic {
fn get_severity(&self) -> Severity;
fn to_diagnostic_frame(&self) -> DiagnosticFrame;
}
pub trait FileCache {
fn fetch(&self, ctx: SyntaxCtxIndex) -> Option<(PathBuf, &String)>;
}
impl DiagnosticFrame {
pub fn subtitles(&self) -> Subtitles {
Subtitles(&self.subtitles)
}
pub fn hints(&self) -> Hints {
Hints(&self.hints)
}
pub fn header(&self) -> Header {
Header {
severity: &self.severity,
title: &self.title
}
}
pub fn markers(&self) -> Markers {
Markers(&self.positions)
}
}

View File

@ -2,6 +2,7 @@ use yansi::Paint;
/// Data structures
pub mod data;
/// Render
pub mod report;
@ -30,6 +31,7 @@ impl Chars {
bullet: '',
}
}
pub fn ascii() -> &'static Chars {
&Chars {
vbar: '|',
@ -48,6 +50,7 @@ impl Chars {
pub struct RenderConfig<'a> {
pub chars: &'a Chars,
pub indent: usize,
}
impl<'a> RenderConfig<'a> {
@ -57,6 +60,7 @@ impl<'a> RenderConfig<'a> {
indent,
}
}
pub fn ascii(indent: usize) -> RenderConfig<'a> {
RenderConfig {
chars: Chars::ascii(),

View File

@ -1,552 +0,0 @@
//! Renders error messages.
// The code is not so good ..
// pretty printers are always a disaster to write. expect
// that in the future i can rewrite it in a better way.
use std::fmt::{Display, Write};
use std::path::{Path, PathBuf};
use std::str;
use fxhash::{FxHashMap, FxHashSet};
use kind_span::{Pos, SyntaxCtxIndex};
use unicode_width::UnicodeWidthStr;
use yansi::Paint;
use crate::{data::*, RenderConfig};
type SortedMarkers = FxHashMap<SyntaxCtxIndex, Vec<Marker>>;
#[derive(Debug, Clone)]
struct Point {
pub line: usize,
pub column: usize,
}
pub trait FileCache {
fn fetch(&self, ctx: SyntaxCtxIndex) -> Option<(PathBuf, &String)>;
}
impl Display for Point {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.line + 1, self.column + 1)
}
}
fn count_width(str: &str) -> (usize, usize) {
(UnicodeWidthStr::width(str), str.chars().filter(|x| *x == '\t').count())
}
fn group_markers(markers: &[Marker]) -> SortedMarkers {
let mut file_group = SortedMarkers::default();
for marker in markers {
let group = file_group
.entry(marker.position.ctx)
.or_insert_with(Vec::new);
group.push(marker.clone())
}
for group in file_group.values_mut() {
group.sort_by(|x, y| x.position.start.cmp(&y.position.end));
}
file_group
}
fn get_code_line_guide(code: &str) -> Vec<usize> {
let mut guide = Vec::new();
let mut size = 0;
for chr in code.chars() {
size += chr.len_utf8();
if chr == '\n' {
guide.push(size);
}
}
guide.push(code.len());
guide
}
fn find_in_line_guide(pos: Pos, guide: &Vec<usize>) -> Point {
for i in 0..guide.len() {
if guide[i] > pos.index as usize {
return Point {
line: i,
column: pos.index as usize - (if i == 0 { 0 } else { guide[i - 1] }),
};
}
}
let line = guide.len() - 1;
Point {
line,
column: pos.index as usize - (if line == 0 { 0 } else { guide[line - 1] }),
}
}
// Get color
fn get_colorizer<T>(color: &Color) -> &dyn Fn(T) -> Paint<T> {
match color {
Color::Fst => &|str| yansi::Paint::red(str).bold(),
Color::Snd => &|str| yansi::Paint::blue(str).bold(),
Color::Thr => &|str| yansi::Paint::green(str).bold(),
Color::For => &|str| yansi::Paint::yellow(str).bold(),
Color::Fft => &|str| yansi::Paint::cyan(str).bold(),
}
}
// TODO: Remove common indentation.
// TODO: Prioritize inline marcations.
fn colorize_code<T: Write + Sized>(
markers: &mut [&(Point, Point, &Marker)],
code_line: &str,
modify: &dyn Fn(&str) -> String,
fmt: &mut T,
) -> std::fmt::Result {
markers.sort_by(|x, y| x.0.column.cmp(&y.0.column));
let mut start = 0;
for marker in markers {
if start < marker.0.column {
write!(fmt, "{}", modify(&code_line[start..marker.0.column]))?;
start = marker.0.column;
}
let end = if marker.0.line == marker.1.line {
marker.1.column
} else {
code_line.len()
};
if start < end {
let colorizer = get_colorizer(&marker.2.color);
write!(fmt, "{}", colorizer(&code_line[start..end]).bold())?;
start = end;
}
}
if start < code_line.len() {
write!(fmt, "{}", modify(&code_line[start..code_line.len()]))?;
}
writeln!(fmt)?;
Ok(())
}
fn paint_line<T>(data: T) -> Paint<T> {
Paint::new(data).fg(yansi::Color::Cyan).dimmed()
}
fn mark_inlined<T: Write + Sized>(
prefix: &str,
code: &str,
config: &RenderConfig,
inline_markers: &mut [&(Point, Point, &Marker)],
fmt: &mut T,
) -> std::fmt::Result {
inline_markers.sort_by(|x, y| x.0.column.cmp(&y.0.column));
let mut start = 0;
write!(
fmt,
"{:>5} {} {}",
"",
paint_line(config.chars.vbar),
prefix
)?;
for marker in inline_markers.iter_mut() {
if start < marker.0.column {
let (pad, tab_pad) = count_width(&code[start..marker.0.column]);
write!(fmt, "{:pad$}{}", "", "\t".repeat(tab_pad), pad = pad)?;
start = marker.0.column;
}
if start < marker.1.column {
let (pad, tab_pad) = count_width(&code[start..marker.1.column]);
let colorizer = get_colorizer(&marker.2.color);
write!(fmt, "{}", colorizer(config.chars.bxline.to_string()))?;
write!(
fmt,
"{}",
colorizer(config.chars.hbar.to_string().repeat((pad + tab_pad).saturating_sub(1)))
)?;
start = marker.1.column;
}
}
writeln!(fmt)?;
// Pretty print the marker
for i in 0..inline_markers.len() {
write!(
fmt,
"{:>5} {} {}",
"",
paint_line(config.chars.vbar),
prefix
)?;
let mut start = 0;
for j in 0..(inline_markers.len() - i) {
let marker = inline_markers[j];
if start < marker.0.column {
let (pad, tab_pad) = count_width(&code[start..marker.0.column]);
write!(fmt, "{:pad$}{}", "", "\t".repeat(tab_pad), pad = pad)?;
start = marker.0.column;
}
if start < marker.1.column {
let colorizer = get_colorizer(&marker.2.color);
if j == (inline_markers.len() - i).saturating_sub(1) {
write!(
fmt,
"{}",
colorizer(format!("{}{}", config.chars.trline, marker.2.text))
)?;
} else {
write!(fmt, "{}", colorizer(config.chars.vbar.to_string()))?;
}
start += 1;
}
}
writeln!(fmt)?;
}
Ok(())
}
fn write_code_block<'a, T: Write + Sized>(
file_name: &Path,
config: &RenderConfig,
markers: &[Marker],
group_code: &'a str,
fmt: &mut T,
) -> std::fmt::Result {
let guide = get_code_line_guide(group_code);
let point = find_in_line_guide(markers[0].position.start, &guide);
let no_code = markers.iter().all(|x| x.no_code);
let header = format!(
"{:>5} {}{}[{}:{}]",
"",
if no_code {
config.chars.hbar
} else {
config.chars.brline
},
config.chars.hbar.to_string().repeat(2),
file_name.to_str().unwrap(),
point
);
writeln!(fmt, "{}", paint_line(header))?;
if no_code {
return Ok(());
}
writeln!(fmt, "{:>5} {}", "", paint_line(config.chars.vbar))?;
let mut lines_set = FxHashSet::default();
let mut markers_by_line: FxHashMap<usize, Vec<(Point, Point, &Marker)>> = FxHashMap::default();
let mut multi_line_markers: Vec<(Point, Point, &Marker)> = Vec::new();
for marker in markers {
let start = find_in_line_guide(marker.position.start, &guide);
let end = find_in_line_guide(marker.position.end, &guide);
if let Some(row) = markers_by_line.get_mut(&start.line) {
row.push((start.clone(), end.clone(), marker))
} else {
markers_by_line.insert(start.line, vec![(start.clone(), end.clone(), marker)]);
}
if end.line != start.line {
multi_line_markers.push((start.clone(), end.clone(), marker));
} else if marker.main {
// Just to make errors a little bit better
let start = start.line.saturating_sub(1);
let end = if start + 2 >= guide.len() {
guide.len() - 1
} else {
start + 2
};
for i in start..=end {
lines_set.insert(i);
}
}
if end.line - start.line <= 3 {
for i in start.line..=end.line {
lines_set.insert(i);
}
} else {
lines_set.insert(start.line);
lines_set.insert(end.line);
}
}
let code_lines: Vec<&'a str> = group_code.lines().collect();
let mut lines = lines_set
.iter()
.filter(|x| **x < code_lines.len())
.collect::<Vec<&usize>>();
lines.sort();
for i in 0..lines.len() {
let line = lines[i];
let mut prefix = " ".to_string();
let mut empty_vec = Vec::new();
let row = markers_by_line.get_mut(line).unwrap_or(&mut empty_vec);
let mut inline_markers: Vec<&(Point, Point, &Marker)> =
row.iter().filter(|x| x.0.line == x.1.line).collect();
let mut current = None;
for marker in &multi_line_markers {
if marker.0.line == *line {
writeln!(
fmt,
"{:>5} {} {} ",
"",
paint_line(config.chars.vbar),
get_colorizer(&marker.2.color)(config.chars.brline)
)?;
}
if *line >= marker.0.line && *line <= marker.1.line {
prefix = format!(" {} ", get_colorizer(&marker.2.color)(config.chars.vbar));
current = Some(marker);
break;
}
}
write!(
fmt,
"{:>5} {} {}",
line + 1,
paint_line(config.chars.vbar),
prefix,
)?;
let modify: Box<dyn Fn(&str) -> String> = if let Some(marker) = current {
prefix = format!(" {} ", get_colorizer(&marker.2.color)(config.chars.vbar));
Box::new(|str: &str| get_colorizer(&marker.2.color)(str).to_string())
} else {
Box::new(|str: &str| str.to_string())
};
if !inline_markers.is_empty() {
colorize_code(&mut inline_markers, code_lines[*line], &modify, fmt)?;
mark_inlined(&prefix, code_lines[*line], config, &mut inline_markers, fmt)?;
if markers_by_line.contains_key(&(line + 1)) {
writeln!(
fmt,
"{:>5} {} {} ",
"",
paint_line(config.chars.dbar),
prefix
)?;
}
} else {
writeln!(fmt, "{}", modify(code_lines[*line]))?;
}
if let Some(marker) = current {
if marker.1.line == *line {
let col = get_colorizer(&marker.2.color);
writeln!(
fmt,
"{:>5} {} {} ",
"",
paint_line(config.chars.dbar),
prefix
)?;
writeln!(
fmt,
"{:>5} {} {} ",
"",
paint_line(config.chars.dbar),
col(format!(" {} {}", config.chars.trline, marker.2.text))
)?;
prefix = " ".to_string();
}
}
if i < lines.len() - 1 && lines[i + 1] - line > 1 {
writeln!(
fmt,
"{:>5} {} {} ",
"",
paint_line(config.chars.dbar),
prefix
)?;
}
}
Ok(())
}
fn render_tag<T: Write + Sized>(severity: &Severity, fmt: &mut T) -> std::fmt::Result {
write!(
fmt,
" {} ",
match severity {
Severity::Error => Paint::new(" ERROR ").bg(yansi::Color::Red).bold(),
Severity::Warning => Paint::new(" WARN ").bg(yansi::Color::Yellow).bold(),
Severity::Info => Paint::new(" INFO ").bg(yansi::Color::Blue).bold(),
}
)
}
pub trait Report {
fn render<T: Write + Sized, C: FileCache>(
&self,
cache: &C,
config: &RenderConfig,
fmt: &mut T,
) -> std::fmt::Result;
}
impl Report for Box<dyn Diagnostic> {
fn render<T: Write + Sized, C: FileCache>(
&self,
cache: &C,
config: &RenderConfig,
fmt: &mut T,
) -> std::fmt::Result {
write!(fmt, " ")?;
let frame = self.to_diagnostic_frame();
render_tag(&frame.severity, fmt)?;
writeln!(fmt, "{}", Paint::new(&frame.title).bold())?;
if !frame.subtitles.is_empty() {
writeln!(fmt)?;
}
for subtitle in &frame.subtitles {
match subtitle {
Subtitle::Normal(color, phr) => {
let colorizer = get_colorizer(color);
writeln!(
fmt,
"{:>5} {} {}",
"",
colorizer(config.chars.bullet),
Paint::new(phr)
)?;
}
Subtitle::Bold(color, phr) => {
let colorizer = get_colorizer(color);
writeln!(
fmt,
"{:>5} {} {}",
"",
colorizer(config.chars.bullet),
Paint::new(phr).bold()
)?;
}
Subtitle::Phrase(color, words) => {
let colorizer = get_colorizer(color);
write!(fmt, "{:>5} {} ", "", colorizer(config.chars.bullet))?;
for word in words {
match word {
Word::Normal(str) => write!(fmt, "{} ", Paint::new(str))?,
Word::Dimmed(str) => write!(fmt, "{} ", Paint::new(str).dimmed())?,
Word::White(str) => write!(fmt, "{} ", Paint::new(str).bold())?,
Word::Painted(color, str) => {
let colorizer = get_colorizer(color);
write!(fmt, "{} ", colorizer(str))?
}
}
}
writeln!(fmt)?;
}
Subtitle::LineBreak => {
writeln!(fmt)?;
}
}
}
let groups = group_markers(&frame.positions);
let is_empty = groups.is_empty();
for (ctx, group) in groups {
writeln!(fmt)?;
let (file, code) = cache.fetch(ctx).unwrap();
let diff =
pathdiff::diff_paths(&file.clone(), PathBuf::from(".").canonicalize().unwrap())
.unwrap_or(file);
write_code_block(&diff, config, &group, code, fmt)?;
}
if !is_empty {
writeln!(fmt)?;
}
for hint in &frame.hints {
writeln!(
fmt,
"{:>5} {} {}",
"",
Paint::new("Hint:").fg(yansi::Color::Cyan).bold(),
Paint::new(hint).fg(yansi::Color::Cyan)
)?;
}
writeln!(fmt)?;
Ok(())
}
}
impl Report for Log {
fn render<T: Write + Sized, C: FileCache>(
&self,
_cache: &C,
_config: &RenderConfig,
fmt: &mut T,
) -> std::fmt::Result {
match self {
Log::Checking(file) => {
writeln!(
fmt,
" {} {}",
Paint::new(" CHECKING ").bg(yansi::Color::Green).bold(),
file
)
}
Log::Compiled(duration) => {
writeln!(
fmt,
" {} All relevant terms compiled. took {:.2}s",
Paint::new(" COMPILED ").bg(yansi::Color::Green).bold(),
duration.as_secs_f32()
)
}
Log::Checked(duration) => {
writeln!(
fmt,
" {} All terms checked. took {:.2}s",
Paint::new(" CHECKED ").bg(yansi::Color::Green).bold(),
duration.as_secs_f32()
)
}
Log::Failed(duration) => {
writeln!(
fmt,
" {} Took {}s",
Paint::new(" FAILED ").bg(yansi::Color::Red).bold(),
duration.as_secs()
)
}
Log::Rewrites(u64) => {
writeln!(
fmt,
" {} Rewrites: {}",
Paint::new(" STATS ").bg(yansi::Color::Green).bold(),
u64
)
}
}
}
}

View File

@ -0,0 +1,107 @@
use fxhash::FxHashMap;
use kind_span::{Pos, SyntaxCtxIndex};
use std::{collections::hash_map::Iter, fmt::Display};
use unicode_width::UnicodeWidthStr;
use crate::data::Marker;
/// The line guide is useful to locate some positions inside the source
/// code by using the index instead of line and column information.
pub struct LineGuide(Vec<usize>);
pub struct FileMarkers(pub Vec<Marker>);
/// This structure contains all markers sorted by lines and column for each
/// one of the files.
pub struct SortedMarkers(FxHashMap<SyntaxCtxIndex, FileMarkers>);
impl SortedMarkers {
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn iter(&self) -> Iter<SyntaxCtxIndex, FileMarkers> {
self.0.iter()
}
}
#[derive(Clone, Copy)]
pub struct Point {
pub line: usize,
pub column: usize,
}
impl Display for Point {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.line + 1, self.column + 1)
}
}
pub struct Spaces {
pub width: usize,
pub tabs: usize,
}
impl LineGuide {
pub fn get(code: &str) -> LineGuide {
let mut guide = Vec::new();
let mut size = 0;
for chr in code.chars() {
size += chr.len_utf8();
if chr == '\n' {
guide.push(size);
}
}
guide.push(code.len());
LineGuide(guide)
}
pub fn find(&self, pos: Pos) -> Point {
for i in 0..self.0.len() {
if self.0[i] > pos.index as usize {
return Point {
line: i,
column: pos.index as usize - (if i == 0 { 0 } else { self.0[i - 1] }),
};
}
}
let line = self.0.len() - 1;
Point {
line,
column: pos.index as usize - (if line == 0 { 0 } else { self.0[line - 1] }),
}
}
pub fn len(&self) -> usize {
self.0.len()
}
}
pub fn count_width(str: &str) -> Spaces {
Spaces {
width: UnicodeWidthStr::width(str),
tabs: str.chars().filter(|x| *x == '\t').count(),
}
}
pub fn group_markers(markers: &[Marker]) -> SortedMarkers {
let mut file_group = FxHashMap::default();
for marker in markers {
let group = file_group
.entry(marker.position.ctx)
.or_insert_with(Vec::new);
group.push(marker.clone())
}
for group in file_group.values_mut() {
group.sort_by(|x, y| x.position.start.cmp(&y.position.end));
}
SortedMarkers(
file_group
.into_iter()
.map(|(x, y)| (x, FileMarkers(y)))
.collect(),
)
}

View File

@ -0,0 +1,5 @@
pub mod code;
pub mod mode;
pub use code::*;
pub use mode::*;

View File

@ -0,0 +1,504 @@
use super::CodeBlock;
use super::{Classic, Renderable, Res};
use crate::data::*;
use crate::report::code::{count_width, group_markers, LineGuide, Spaces};
use crate::report::code::{FileMarkers, Point};
use crate::RenderConfig;
use fxhash::{FxHashMap, FxHashSet};
use pathdiff::diff_paths;
use std::fmt::Write;
use std::path::PathBuf;
use yansi::Paint;
fn colorize_code<T: Write + Sized>(
markers: &mut [&(Point, Point, &Marker)],
code_line: &str,
modify: &dyn Fn(&str) -> String,
fmt: &mut T,
) -> std::fmt::Result {
markers.sort_by(|x, y| x.0.column.cmp(&y.0.column));
let mut start = 0;
for marker in markers {
if start < marker.0.column {
write!(fmt, "{}", modify(&code_line[start..marker.0.column]))?;
start = marker.0.column;
}
let end = if marker.0.line == marker.1.line {
marker.1.column
} else {
code_line.len()
};
if start < end {
let colorizer = &marker.2.color.colorizer();
write!(fmt, "{}", colorizer(&code_line[start..end]).bold())?;
start = end;
}
}
if start < code_line.len() {
write!(fmt, "{}", modify(&code_line[start..code_line.len()]))?;
}
writeln!(fmt)?;
Ok(())
}
fn mark_inlined<T: Write + Sized>(
prefix: &str,
code: &str,
config: &RenderConfig,
inline_markers: &mut [&(Point, Point, &Marker)],
fmt: &mut T,
) -> std::fmt::Result {
inline_markers.sort_by(|x, y| x.0.column.cmp(&y.0.column));
let mut start = 0;
write!(
fmt,
"{:>5} {} {}",
"",
paint_line(config.chars.vbar),
prefix
)?;
for marker in inline_markers.iter_mut() {
if start < marker.0.column {
let Spaces { width, tabs } = count_width(&code[start..marker.0.column]);
write!(fmt, "{:pad$}{}", "", "\t".repeat(tabs), pad = width)?;
start = marker.0.column;
}
if start < marker.1.column {
let Spaces { width, tabs } = count_width(&code[start..marker.1.column]);
let colorizer = marker.2.color.colorizer();
write!(fmt, "{}", colorizer(config.chars.bxline.to_string()))?;
write!(
fmt,
"{}",
colorizer(
config
.chars
.hbar
.to_string()
.repeat((width + tabs).saturating_sub(1))
)
)?;
start = marker.1.column;
}
}
writeln!(fmt)?;
// Pretty print the marker
for i in 0..inline_markers.len() {
write!(
fmt,
"{:>5} {} {}",
"",
paint_line(config.chars.vbar),
prefix
)?;
let mut start = 0;
for j in 0..(inline_markers.len() - i) {
let marker = inline_markers[j];
if start < marker.0.column {
let Spaces { width, tabs } = count_width(&code[start..marker.0.column]);
write!(fmt, "{:pad$}{}", "", "\t".repeat(tabs), pad = width)?;
start = marker.0.column;
}
if start < marker.1.column {
let colorizer = marker.2.color.colorizer();
if j == (inline_markers.len() - i).saturating_sub(1) {
write!(
fmt,
"{}",
colorizer(format!("{}{}", config.chars.trline, marker.2.text))
)?;
} else {
write!(fmt, "{}", colorizer(config.chars.vbar.to_string()))?;
}
start += 1;
}
}
writeln!(fmt)?;
}
Ok(())
}
fn group_marker_lines<'a>(
guide: &'a LineGuide,
markers: &'a FileMarkers,
) -> (
FxHashSet<usize>,
FxHashMap<usize, Vec<(Point, Point, &'a Marker)>>,
Vec<(Point, Point, &'a Marker)>,
) {
let mut lines_set = FxHashSet::default();
let mut markers_by_line: FxHashMap<usize, Vec<(Point, Point, &Marker)>> = FxHashMap::default();
let mut multi_line_markers: Vec<(Point, Point, &Marker)> = Vec::new();
for marker in &markers.0 {
let start = guide.find(marker.position.start);
let end = guide.find(marker.position.end);
if let Some(row) = markers_by_line.get_mut(&start.line) {
row.push((start.clone(), end.clone(), &marker))
} else {
markers_by_line.insert(start.line, vec![(start.clone(), end.clone(), &marker)]);
}
if end.line != start.line {
multi_line_markers.push((start.clone(), end.clone(), &marker));
} else if marker.main {
// Just to make errors a little bit better
let start = start.line.saturating_sub(1);
let end = if start + 2 >= guide.len() {
guide.len() - 1
} else {
start + 2
};
for i in start..=end {
lines_set.insert(i);
}
}
if end.line - start.line <= 3 {
for i in start.line..=end.line {
lines_set.insert(i);
}
} else {
lines_set.insert(start.line);
lines_set.insert(end.line);
}
}
(lines_set, markers_by_line, multi_line_markers)
}
fn paint_line<T>(data: T) -> Paint<T> {
Paint::new(data).fg(yansi::Color::Cyan).dimmed()
}
impl Color {
fn colorizer<T>(&self) -> &dyn Fn(T) -> Paint<T> {
match self {
Color::Fst => &|str| yansi::Paint::red(str).bold(),
Color::Snd => &|str| yansi::Paint::blue(str).bold(),
Color::Thr => &|str| yansi::Paint::green(str).bold(),
Color::For => &|str| yansi::Paint::yellow(str).bold(),
Color::Fft => &|str| yansi::Paint::cyan(str).bold(),
}
}
fn colorize<T>(&self, data: T) -> Paint<T> {
(self.colorizer())(data)
}
}
impl Renderable<Classic> for Severity {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, _: &C, _: &RenderConfig) -> Res {
use Severity::*;
let painted = match self {
Error => Paint::new(" ERROR ").bg(yansi::Color::Red).bold(),
Warning => Paint::new(" WARN ").bg(yansi::Color::Yellow).bold(),
Info => Paint::new(" INFO ").bg(yansi::Color::Blue).bold(),
};
write!(fmt, " {} ", painted)
}
}
impl<'a> Renderable<Classic> for Header<'a> {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, cache: &C, config: &RenderConfig) -> Res {
self.severity.render(fmt, cache, config)?;
fmt.write_str(&Paint::new(&self.title).bold().to_string())?;
fmt.write_char('\n')
}
}
impl Renderable<Classic> for Subtitle {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, cache: &C, config: &RenderConfig) -> Res {
match self {
Subtitle::Normal(color, phr) => {
let bullet = color.colorize(config.chars.bullet);
writeln!(fmt, "{:>5} {} {}", "", bullet, Paint::new(phr))
}
Subtitle::Bold(color, phr) => {
let bullet = color.colorize(config.chars.bullet);
writeln!(fmt, "{:>5} {} {}", "", bullet, Paint::new(phr).bold())
}
Subtitle::Phrase(color, words) => {
let bullet = color.colorize(config.chars.bullet);
write!(fmt, "{:>5} {} ", "", bullet)?;
words.render(fmt, cache, config)?;
writeln!(fmt)
}
Subtitle::LineBreak => {
writeln!(fmt)
}
}
}
}
impl<'a> Renderable<Classic> for Word {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, _: &C, _: &RenderConfig) -> Res {
match self {
Word::Normal(str) => write!(fmt, "{} ", Paint::new(str)),
Word::Dimmed(str) => write!(fmt, "{} ", Paint::new(str).dimmed()),
Word::White(str) => write!(fmt, "{} ", Paint::new(str).bold()),
Word::Painted(color, str) => write!(fmt, "{} ", color.colorize(str)),
}
}
}
impl<'a> Renderable<Classic> for Subtitles<'a> {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, cache: &C, config: &RenderConfig) -> Res {
if !self.0.is_empty() {
writeln!(fmt)?;
}
self.0.render(fmt, cache, config)
}
}
impl<'a> Renderable<Classic> for CodeBlock<'a> {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, _: &C, config: &RenderConfig) -> Res {
let guide = LineGuide::get(self.code);
let point = guide.find(self.markers.0[0].position.start);
let chars = config.chars;
// Header of the code block
let bars = chars.hbar.to_string().repeat(2);
let file = self.path.to_str().unwrap();
let header = format!("{:>5} {}{}[{}:{}]", "", chars.brline, bars, file, point);
writeln!(fmt, "{}", paint_line(header))?;
if self.markers.0.iter().all(|x| x.no_code) {
return Ok(());
}
writeln!(fmt, "{:>5} {}", "", paint_line(chars.vbar))?;
let (lines_set, mut by_line, multi_line) = group_marker_lines(&guide, self.markers);
let code_lines: Vec<&'a str> = self.code.lines().collect();
let mut lines: Vec<usize> = lines_set
.into_iter()
.filter(|x| *x < code_lines.len())
.collect();
lines.sort();
for i in 0..lines.len() {
let line = lines[i];
let mut prefix = " ".to_string();
let mut empty_vec = Vec::new();
let row = by_line.get_mut(&line).unwrap_or(&mut empty_vec);
let mut inline_markers: Vec<&(Point, Point, &Marker)> =
row.iter().filter(|x| x.0.line == x.1.line).collect();
let mut current = None;
for marker in &multi_line {
if marker.0.line == line {
writeln!(
fmt,
"{:>5} {} {} ",
"",
paint_line(config.chars.vbar),
marker.2.color.colorize(config.chars.brline)
)?;
}
if line >= marker.0.line && line <= marker.1.line {
prefix = format!(" {} ", marker.2.color.colorize(config.chars.vbar));
current = Some(marker);
break;
}
}
write!(
fmt,
"{:>5} {} {}",
line + 1,
paint_line(config.chars.vbar),
prefix,
)?;
let modify: Box<dyn Fn(&str) -> String> = if let Some(marker) = current {
prefix = format!(" {} ", marker.2.color.colorize(config.chars.vbar));
Box::new(|str: &str| marker.2.color.colorize(str).to_string())
} else {
Box::new(|str: &str| str.to_string())
};
if !inline_markers.is_empty() {
colorize_code(&mut inline_markers, code_lines[line], &modify, fmt)?;
mark_inlined(&prefix, code_lines[line], config, &mut inline_markers, fmt)?;
if by_line.contains_key(&(line + 1)) {
writeln!(
fmt,
"{:>5} {} {} ",
"",
paint_line(config.chars.dbar),
prefix
)?;
}
} else {
writeln!(fmt, "{}", modify(code_lines[line]))?;
}
if let Some(marker) = current {
if marker.1.line == line {
let col = marker.2.color.colorizer();
writeln!(
fmt,
"{:>5} {} {} ",
"",
paint_line(config.chars.dbar),
prefix
)?;
writeln!(
fmt,
"{:>5} {} {} ",
"",
paint_line(config.chars.dbar),
col(format!(" {} {}", config.chars.trline, marker.2.text))
)?;
prefix = " ".to_string();
}
}
if i < lines.len() - 1 && lines[i + 1] - line > 1 {
writeln!(
fmt,
"{:>5} {} {} ",
"",
paint_line(config.chars.dbar),
prefix
)?;
}
}
Ok(())
}
}
impl Renderable<Classic> for Log {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, _: &C, _: &RenderConfig) -> Res {
match self {
Log::Checking(file) => {
writeln!(
fmt,
" {} {}",
Paint::new(" CHECKING ").bg(yansi::Color::Green).bold(),
file
)
}
Log::Compiled(duration) => {
writeln!(
fmt,
" {} All relevant terms compiled. took {:.2}s",
Paint::new(" COMPILED ").bg(yansi::Color::Green).bold(),
duration.as_secs_f32()
)
}
Log::Checked(duration) => {
writeln!(
fmt,
" {} All terms checked. took {:.2}s",
Paint::new(" CHECKED ").bg(yansi::Color::Green).bold(),
duration.as_secs_f32()
)
}
Log::Failed(duration) => {
writeln!(
fmt,
" {} Took {}s",
Paint::new(" FAILED ").bg(yansi::Color::Red).bold(),
duration.as_secs()
)
}
Log::Rewrites(u64) => {
writeln!(
fmt,
" {} Rewrites: {}",
Paint::new(" STATS ").bg(yansi::Color::Green).bold(),
u64
)
}
}
}
}
impl<'a> Renderable<Classic> for Markers<'a> {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, cache: &C, config: &RenderConfig) -> Res {
let groups = group_markers(&self.0);
let is_empty = groups.is_empty();
let current = PathBuf::from(".").canonicalize().unwrap();
for (ctx, markers) in groups.iter() {
writeln!(fmt)?;
let (file, code) = cache.fetch(*ctx).unwrap();
let path = diff_paths(&file.clone(), current.clone()).unwrap_or(file);
let block = CodeBlock {
code,
path: &path,
markers,
};
block.render(fmt, cache, config)?;
}
if !is_empty {
writeln!(fmt)?;
}
Ok(())
}
}
impl<'a> Renderable<Classic> for Hints<'a> {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, _: &C, _: &RenderConfig) -> Res {
for hint in self.0 {
writeln!(
fmt,
"{:>5} {} {}",
"",
Paint::new("Hint:").fg(yansi::Color::Cyan).bold(),
Paint::new(hint).fg(yansi::Color::Cyan)
)?;
}
writeln!(fmt)
}
}
impl Renderable<Classic> for DiagnosticFrame {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, cache: &C, config: &RenderConfig) -> Res {
write!(fmt, " ")?;
self.header().render(fmt, cache, config)?;
self.subtitles().render(fmt, cache, config)?;
self.markers().render(fmt, cache, config)?;
self.hints().render(fmt, cache, config)?;
Ok(())
}
}
impl<T> Renderable<T> for Box<dyn Diagnostic> {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, cache: &C, config: &RenderConfig) -> Res {
self.to_diagnostic_frame().render(fmt, cache, config)
}
}

View File

@ -0,0 +1,47 @@
use std::{fmt::Write, path::Path};
use crate::{data::FileCache, RenderConfig};
use super::code::FileMarkers;
pub mod classic;
// Just a type synonym to make it easier to read.
pub type Res = std::fmt::Result;
// -----------------------------------------------------------------
// Some abstract data types based on Haskell. These types are useful
// for setting some modes on the report.
// -----------------------------------------------------------------
/// Classical mode is the default mode for the report. It's made to
/// be easy to read and understand.
pub enum Classic {}
/// Compact mode is made to be more compact and easy to parse by some
/// LLM.
pub enum Compact {}
// Utilities
/// Utility for easier renders
pub(crate) struct CodeBlock<'a> {
pub code: &'a str,
pub path: &'a Path,
pub markers: &'a FileMarkers
}
/// A type class for renderable error reports and messages. It's useful
/// to change easily things without problems.
pub trait Renderable<T> {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, cache: &C, config: &RenderConfig) -> Res;
}
impl<'a, T, E> Renderable<T> for Vec<E> where E : Renderable<T> {
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, cache: &C, config: &RenderConfig) -> Res {
for elem in self {
elem.render(fmt, cache, config)?;
}
Ok(())
}
}

View File

@ -2,7 +2,7 @@
use kind_driver::session::Session;
use kind_report::data::Diagnostic;
use kind_report::report::Report;
use kind_report::report::{Renderable, Classic};
use kind_report::RenderConfig;
use std::fs::{self, File};
@ -50,7 +50,7 @@ fn test_kind2(path: &Path, run: fn(&PathBuf, &mut Session) -> Option<String>) ->
let mut res_string = String::new();
for diag in diagnostics {
diag.render(&session, &render, &mut res_string).unwrap();
Renderable::<Classic>::render(&diag, &mut res_string, &session, &render).unwrap();
}
res_string