mirror of
https://github.com/Kindelia/Kind2.git
synced 2024-10-05 20:07:27 +03:00
Merge pull request #553 from HigherOrderCO/552-add-new-type-of-error-messages-used-for-llms
Add new type of error messages used for llms
This commit is contained in:
commit
3a2ae5e93d
30
Cargo.lock
generated
30
Cargo.lock
generated
@ -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",
|
||||
|
@ -1,6 +1,9 @@
|
||||
//! Errors created by the type checker.
|
||||
|
||||
use kind_report::data::{Color, Diagnostic, DiagnosticFrame, Marker, Severity, Subtitle, Word};
|
||||
use kind_report::{
|
||||
data::{Color, Diagnostic, DiagnosticFrame, Marker, Severity, Subtitle, Word},
|
||||
RenderConfig,
|
||||
};
|
||||
use kind_span::Range;
|
||||
use kind_tree::desugared::Expr;
|
||||
|
||||
@ -16,40 +19,43 @@ pub(crate) enum TypeDiagnostic {
|
||||
Inspection(Context, Range, Box<Expr>),
|
||||
TooManyArguments(Context, Range),
|
||||
TypeMismatch(Context, Range, Box<Expr>, Box<Expr>),
|
||||
UncoveredPattern(Context, Range, Vec<Box<Expr>>)
|
||||
UncoveredPattern(Context, Range, Vec<Box<Expr>>),
|
||||
}
|
||||
|
||||
fn context_to_subtitles(ctx: &Context, subtitles: &mut Vec<Subtitle>) {
|
||||
fn context_to_subtitles(config: &RenderConfig, ctx: &Context, subtitles: &mut Vec<Subtitle>) {
|
||||
subtitles.push(Subtitle::LineBreak);
|
||||
|
||||
if !ctx.0.is_empty() {
|
||||
subtitles.push(Subtitle::Phrase(
|
||||
Color::Snd,
|
||||
vec![Word::White("Context:".to_string())],
|
||||
));
|
||||
subtitles.push(Subtitle::Field(Color::Snd, "Context".to_string()));
|
||||
}
|
||||
|
||||
let biggest = ctx
|
||||
.0
|
||||
.iter()
|
||||
.max_by_key(|p| p.0.len())
|
||||
.map(|x| x.0.len())
|
||||
.unwrap_or(0);
|
||||
let biggest = if config.not_align {
|
||||
0
|
||||
} else {
|
||||
ctx.0
|
||||
.iter()
|
||||
.max_by_key(|p| p.0.len())
|
||||
.map(|x| x.0.len())
|
||||
.unwrap_or(0)
|
||||
};
|
||||
|
||||
for (name, typ, vals) in &ctx.0 {
|
||||
subtitles.push(Subtitle::Phrase(
|
||||
Color::Snd,
|
||||
vec![
|
||||
Word::Dimmed(" ".to_string()),
|
||||
Word::White(format!("{:<width$} :", name, width = biggest)),
|
||||
Word::Painted(Color::Snd, typ.to_string()),
|
||||
],
|
||||
));
|
||||
|
||||
if config.hide_vals {
|
||||
continue;
|
||||
}
|
||||
|
||||
for val in vals {
|
||||
subtitles.push(Subtitle::Phrase(
|
||||
Color::Snd,
|
||||
vec![
|
||||
Word::Dimmed(" ".to_string()),
|
||||
Word::Dimmed(format!("{:<width$} =", name, width = biggest)),
|
||||
Word::Dimmed(val.to_string()),
|
||||
],
|
||||
@ -73,7 +79,7 @@ impl Diagnostic for TypeDiagnostic {
|
||||
}
|
||||
}
|
||||
|
||||
fn to_diagnostic_frame(&self) -> DiagnosticFrame {
|
||||
fn to_diagnostic_frame(&self, config: &RenderConfig) -> DiagnosticFrame {
|
||||
match self {
|
||||
TypeDiagnostic::TypeMismatch(ctx, range, detected, expected) => {
|
||||
let mut subtitles = vec![
|
||||
@ -92,7 +98,7 @@ impl Diagnostic for TypeDiagnostic {
|
||||
],
|
||||
),
|
||||
];
|
||||
context_to_subtitles(ctx, &mut subtitles);
|
||||
context_to_subtitles(config, ctx, &mut subtitles);
|
||||
DiagnosticFrame {
|
||||
code: 101,
|
||||
severity: Severity::Error,
|
||||
@ -112,17 +118,17 @@ impl Diagnostic for TypeDiagnostic {
|
||||
let mut subtitles = vec![Subtitle::Phrase(
|
||||
Color::Snd,
|
||||
vec![
|
||||
Word::White("Expected:".to_string()),
|
||||
Word::White("Hole:".to_string()),
|
||||
Word::Painted(Color::Snd, expected.to_string()),
|
||||
],
|
||||
)];
|
||||
|
||||
context_to_subtitles(ctx, &mut subtitles);
|
||||
context_to_subtitles(config, ctx, &mut subtitles);
|
||||
|
||||
DiagnosticFrame {
|
||||
code: 101,
|
||||
severity: Severity::Info,
|
||||
title: "Inspection.".to_string(),
|
||||
title: "Inspection".to_string(),
|
||||
subtitles,
|
||||
hints: vec![],
|
||||
positions: vec![Marker {
|
||||
@ -199,9 +205,16 @@ impl Diagnostic for TypeDiagnostic {
|
||||
Color::For,
|
||||
vec![
|
||||
Word::White("Missing case :".to_string()),
|
||||
Word::Painted(Color::For, terms.iter().map(|x| format!("{}", x)).collect::<Vec<_>>().join(" ")),
|
||||
Word::Painted(
|
||||
Color::For,
|
||||
terms
|
||||
.iter()
|
||||
.map(|x| format!("{}", x))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" "),
|
||||
),
|
||||
],
|
||||
),],
|
||||
)],
|
||||
hints: vec![],
|
||||
positions: vec![Marker {
|
||||
position: *range,
|
||||
|
@ -6,12 +6,15 @@ 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, FileCache, Log, Severity};
|
||||
use kind_report::RenderConfig;
|
||||
|
||||
use kind_driver as driver;
|
||||
|
||||
use kind_report::report::{Classic, Mode, Report};
|
||||
|
||||
pub type CO = Classic;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[clap(propagate_version = true)]
|
||||
@ -46,6 +49,14 @@ pub struct Cli {
|
||||
#[arg(short, long)]
|
||||
pub ascii: bool,
|
||||
|
||||
/// Compact error messages
|
||||
#[arg(long)]
|
||||
pub compact: bool,
|
||||
|
||||
/// Show values in error messages
|
||||
#[arg(long)]
|
||||
pub hide_vals: bool,
|
||||
|
||||
/// Entrypoint of the file that makes the erasure checker
|
||||
/// not remove the entry.
|
||||
#[arg(short, long)]
|
||||
@ -88,7 +99,7 @@ pub enum Command {
|
||||
GenChecker {
|
||||
#[arg(short, long)]
|
||||
coverage: bool,
|
||||
|
||||
|
||||
file: String,
|
||||
},
|
||||
|
||||
@ -129,9 +140,9 @@ where
|
||||
{
|
||||
Report::render(
|
||||
err,
|
||||
&mut ToWriteFmt(std::io::stderr()),
|
||||
session,
|
||||
render_config,
|
||||
&mut ToWriteFmt(std::io::stderr()),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@ -199,7 +210,13 @@ pub fn compile_in_session<T>(
|
||||
pub fn run_cli(config: Cli) -> anyhow::Result<()> {
|
||||
kind_report::check_if_colors_are_supported(config.no_color);
|
||||
|
||||
let render_config = kind_report::check_if_utf8_is_supported(config.ascii, 2);
|
||||
let mode = if config.compact {
|
||||
Mode::Compact
|
||||
} else {
|
||||
Mode::Classic
|
||||
};
|
||||
|
||||
let render_config = kind_report::check_if_utf8_is_supported(config.ascii, 2, config.hide_vals, mode);
|
||||
let root = config.root.unwrap_or_else(|| PathBuf::from("."));
|
||||
|
||||
let mut entrypoints = vec!["Main".to_string()];
|
||||
|
@ -1,4 +1,4 @@
|
||||
use kind_report::data::{Color, Diagnostic, DiagnosticFrame, Marker, Severity};
|
||||
use kind_report::{data::{Color, Diagnostic, DiagnosticFrame, Marker, Severity}, RenderConfig};
|
||||
use kind_span::Range;
|
||||
|
||||
pub(crate) enum DeriveDiagnostic {
|
||||
@ -16,7 +16,7 @@ impl Diagnostic for DeriveDiagnostic {
|
||||
}
|
||||
}
|
||||
|
||||
fn to_diagnostic_frame(&self) -> DiagnosticFrame {
|
||||
fn to_diagnostic_frame(&self, _: &RenderConfig) -> DiagnosticFrame {
|
||||
match self {
|
||||
DeriveDiagnostic::CannotUseNamedVariable(range) => DiagnosticFrame {
|
||||
code: 103,
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
use std::{path::PathBuf, fmt::Display, error::Error};
|
||||
|
||||
use kind_report::data::{Color, Diagnostic, DiagnosticFrame, Marker, Severity, Subtitle, Word};
|
||||
use kind_report::{data::{Color, Diagnostic, DiagnosticFrame, Marker, Severity, Subtitle, Word}, RenderConfig};
|
||||
use kind_tree::symbol::{Ident, QualifiedIdent};
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -38,7 +38,7 @@ impl Diagnostic for DriverDiagnostic {
|
||||
}
|
||||
}
|
||||
|
||||
fn to_diagnostic_frame(&self) -> DiagnosticFrame {
|
||||
fn to_diagnostic_frame(&self, _: &RenderConfig) -> DiagnosticFrame {
|
||||
match self {
|
||||
DriverDiagnostic::UnboundVariable(idents, suggestions) => DiagnosticFrame {
|
||||
code: 100,
|
||||
|
@ -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};
|
||||
|
@ -1,7 +1,7 @@
|
||||
//! All of the sintatic erros both from the
|
||||
//! lexer and the parser.
|
||||
|
||||
use kind_report::data::{Color, Diagnostic, DiagnosticFrame, Marker, Severity};
|
||||
use kind_report::{data::{Color, Diagnostic, DiagnosticFrame, Marker, Severity}, RenderConfig};
|
||||
use kind_span::{Range, SyntaxCtxIndex};
|
||||
|
||||
use crate::lexer::tokens::Token;
|
||||
@ -67,7 +67,7 @@ impl Diagnostic for SyntaxDiagnostic {
|
||||
}
|
||||
}
|
||||
|
||||
fn to_diagnostic_frame(&self) -> DiagnosticFrame {
|
||||
fn to_diagnostic_frame(&self, _: &RenderConfig) -> DiagnosticFrame {
|
||||
match self {
|
||||
SyntaxDiagnostic::UnfinishedString(range) => DiagnosticFrame {
|
||||
code: 1,
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::{fmt::Display, error::Error};
|
||||
|
||||
use kind_report::data::{Color, Diagnostic, DiagnosticFrame, Marker, Severity};
|
||||
use kind_report::{data::{Color, Diagnostic, DiagnosticFrame, Marker, Severity}, RenderConfig};
|
||||
use kind_span::{Range, SyntaxCtxIndex};
|
||||
use kind_tree::symbol::Ident;
|
||||
|
||||
@ -96,7 +96,7 @@ impl Diagnostic for PassDiagnostic {
|
||||
}
|
||||
}
|
||||
|
||||
fn to_diagnostic_frame(&self) -> DiagnosticFrame {
|
||||
fn to_diagnostic_frame(&self, _: &RenderConfig) -> DiagnosticFrame {
|
||||
match self {
|
||||
PassDiagnostic::UnboundVariable(idents, suggestions) => DiagnosticFrame {
|
||||
code: 100,
|
||||
|
@ -15,3 +15,4 @@ pathdiff = "0.2.1"
|
||||
termsize = "0.1"
|
||||
unicode-width = "0.1.10"
|
||||
yansi = "0.5.1"
|
||||
refl = "0.2.1"
|
@ -1,7 +1,9 @@
|
||||
use std::time::Duration;
|
||||
use std::{time::Duration, path::PathBuf};
|
||||
|
||||
use kind_span::{Range, SyntaxCtxIndex};
|
||||
|
||||
use crate::RenderConfig;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Severity {
|
||||
Error,
|
||||
@ -28,6 +30,7 @@ pub enum Word {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Subtitle {
|
||||
Field(Color, String),
|
||||
Normal(Color, String),
|
||||
Bold(Color, String),
|
||||
Phrase(Color, Vec<Word>),
|
||||
@ -52,6 +55,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),
|
||||
@ -59,8 +74,39 @@ pub enum Log {
|
||||
Rewrites(u64),
|
||||
Failed(Duration),
|
||||
}
|
||||
|
||||
pub struct DiagnosticConfig {
|
||||
hide_vals: bool,
|
||||
indent_ctx_vars: bool
|
||||
}
|
||||
|
||||
pub trait Diagnostic {
|
||||
fn get_syntax_ctx(&self) -> Option<SyntaxCtxIndex>;
|
||||
fn get_severity(&self) -> Severity;
|
||||
fn to_diagnostic_frame(&self) -> DiagnosticFrame;
|
||||
fn to_diagnostic_frame(&self, config: &RenderConfig) -> 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)
|
||||
}
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
use report::Mode;
|
||||
use yansi::Paint;
|
||||
|
||||
/// Data structures
|
||||
pub mod data;
|
||||
|
||||
/// Render
|
||||
pub mod report;
|
||||
|
||||
@ -30,6 +32,7 @@ impl Chars {
|
||||
bullet: '•',
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ascii() -> &'static Chars {
|
||||
&Chars {
|
||||
vbar: '|',
|
||||
@ -48,19 +51,39 @@ impl Chars {
|
||||
pub struct RenderConfig<'a> {
|
||||
pub chars: &'a Chars,
|
||||
pub indent: usize,
|
||||
pub hide_vals: bool,
|
||||
pub mode: Mode,
|
||||
pub not_align: bool,
|
||||
}
|
||||
|
||||
impl<'a> RenderConfig<'a> {
|
||||
pub fn unicode(indent: usize) -> RenderConfig<'a> {
|
||||
pub fn unicode(indent: usize, hide_vals: bool) -> RenderConfig<'a> {
|
||||
RenderConfig {
|
||||
chars: Chars::unicode(),
|
||||
indent,
|
||||
hide_vals,
|
||||
mode: Mode::Classic,
|
||||
not_align: false,
|
||||
}
|
||||
}
|
||||
pub fn ascii(indent: usize) -> RenderConfig<'a> {
|
||||
|
||||
pub fn ascii(indent: usize, hide_vals: bool) -> RenderConfig<'a> {
|
||||
RenderConfig {
|
||||
chars: Chars::ascii(),
|
||||
indent,
|
||||
hide_vals,
|
||||
mode: Mode::Classic,
|
||||
not_align: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compact(indent: usize) -> RenderConfig<'a> {
|
||||
RenderConfig {
|
||||
chars: Chars::ascii(),
|
||||
indent,
|
||||
hide_vals: true,
|
||||
mode: Mode::Compact,
|
||||
not_align: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -71,10 +94,15 @@ pub fn check_if_colors_are_supported(disable: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_if_utf8_is_supported<'a>(disable: bool, indent: usize) -> RenderConfig<'a> {
|
||||
if disable || (cfg!(windows) && !Paint::enable_windows_ascii()) {
|
||||
RenderConfig::ascii(indent)
|
||||
} else {
|
||||
RenderConfig::unicode(indent)
|
||||
pub fn check_if_utf8_is_supported<'a>(disable: bool, indent: usize, hide_vals: bool, mode: Mode) -> RenderConfig<'a> {
|
||||
match mode {
|
||||
Mode::Classic => {
|
||||
if disable || (cfg!(windows) && !Paint::enable_windows_ascii()) {
|
||||
RenderConfig::ascii(indent, hide_vals)
|
||||
} else {
|
||||
RenderConfig::unicode(indent, hide_vals)
|
||||
}
|
||||
},
|
||||
Mode::Compact => RenderConfig::compact(0),
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
157
crates/kind-report/src/report/code.rs
Normal file
157
crates/kind-report/src/report/code.rs
Normal file
@ -0,0 +1,157 @@
|
||||
use fxhash::{FxHashMap, FxHashSet};
|
||||
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(),
|
||||
)
|
||||
}
|
||||
|
||||
pub 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)
|
||||
}
|
5
crates/kind-report/src/report/mod.rs
Normal file
5
crates/kind-report/src/report/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
pub mod code;
|
||||
pub mod mode;
|
||||
|
||||
pub use code::*;
|
||||
pub use mode::*;
|
448
crates/kind-report/src/report/mode/classic.rs
Normal file
448
crates/kind-report/src/report/mode/classic.rs
Normal file
@ -0,0 +1,448 @@
|
||||
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 crate::report::group_marker_lines;
|
||||
|
||||
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 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 {
|
||||
Renderable::<Classic>::render(self.severity, 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) | Subtitle::Field(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)?;
|
||||
Renderable::<Classic>::render(words, 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)?;
|
||||
}
|
||||
|
||||
Renderable::<Classic>::render(self.0, 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,
|
||||
};
|
||||
|
||||
Renderable::<Classic>::render(&block, 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, " ")?;
|
||||
|
||||
Renderable::<Classic>::render(&self.header(), fmt, cache, config)?;
|
||||
Renderable::<Classic>::render(&self.subtitles(), fmt, cache, config)?;
|
||||
Renderable::<Classic>::render(&self.markers(), fmt, cache, config)?;
|
||||
Renderable::<Classic>::render(&self.hints(), fmt, cache, config)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
180
crates/kind-report/src/report/mode/compact.rs
Normal file
180
crates/kind-report/src/report/mode/compact.rs
Normal file
@ -0,0 +1,180 @@
|
||||
use std::{fmt::Write, path::PathBuf};
|
||||
|
||||
use pathdiff::diff_paths;
|
||||
|
||||
use crate::data::*;
|
||||
use crate::{report::*, RenderConfig};
|
||||
|
||||
use super::{CodeBlock, Compact, Renderable, Res};
|
||||
|
||||
fn colorize_code<T: Write + Sized>(
|
||||
markers: &mut [&(Point, Point, &Marker)],
|
||||
code_line: &str,
|
||||
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, "{}", &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 {
|
||||
write!(fmt, "{{{{{}}}}}", &code_line[start..end])?;
|
||||
start = end;
|
||||
}
|
||||
}
|
||||
|
||||
if start < code_line.len() {
|
||||
write!(fmt, "{}}}}}", &code_line[start..code_line.len()])?;
|
||||
}
|
||||
|
||||
writeln!(fmt)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl<'a> Renderable<Compact> for Word {
|
||||
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, _: &C, _: &RenderConfig) -> Res {
|
||||
match self {
|
||||
Word::Normal(str) => write!(fmt, "{} ", str),
|
||||
Word::Dimmed(str) => write!(fmt, "{} ", str),
|
||||
Word::White(str) => write!(fmt, "{} ", str),
|
||||
Word::Painted(_, str) => write!(fmt, "{} ", str),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderable<Compact> for Subtitle {
|
||||
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, cache: &C, config: &RenderConfig) -> Res {
|
||||
match self {
|
||||
Subtitle::Field(_, phr) => writeln!(fmt, "{}", phr.to_lowercase()),
|
||||
Subtitle::Normal(_, phr) => writeln!(fmt, "- {}", phr),
|
||||
Subtitle::Bold(_, phr) => writeln!(fmt, "- {}", phr),
|
||||
Subtitle::Phrase(_, words) => {
|
||||
write!(fmt, "- ")?;
|
||||
Renderable::<Compact>::render(words, fmt, cache, config)?;
|
||||
writeln!(fmt)
|
||||
}
|
||||
Subtitle::LineBreak => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Renderable<Compact> 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)?;
|
||||
}
|
||||
|
||||
Renderable::<Compact>::render(self.0, fmt, cache, config)
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderable<Compact> for Severity {
|
||||
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, _: &C, _: &RenderConfig) -> Res {
|
||||
use Severity::*;
|
||||
|
||||
let painted = match self {
|
||||
Error => "error:",
|
||||
Warning => "warn:",
|
||||
Info => "info:",
|
||||
};
|
||||
|
||||
write!(fmt, " {} ", painted)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Renderable<Compact> for Header<'a> {
|
||||
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, _: &C, _: &RenderConfig) -> Res {
|
||||
fmt.write_str(&self.title.to_lowercase())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Renderable<Compact> for CodeBlock<'a> {
|
||||
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, _: &C, _8778: &RenderConfig) -> Res {
|
||||
writeln!(fmt, "location")?;
|
||||
let guide = LineGuide::get(self.code);
|
||||
|
||||
let (lines_set, mut by_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 line in lines {
|
||||
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();
|
||||
|
||||
if !inline_markers.is_empty() {
|
||||
colorize_code(&mut inline_markers, code_lines[line], fmt)?;
|
||||
} else {
|
||||
writeln!(fmt, "{}", code_lines[line])?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Renderable<Compact> 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 current = PathBuf::from(".").canonicalize().unwrap();
|
||||
|
||||
for (ctx, markers) in groups.iter() {
|
||||
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,
|
||||
};
|
||||
|
||||
Renderable::<Compact>::render(&block, fmt, cache, config)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderable<Compact> for DiagnosticFrame {
|
||||
fn render<U: std::fmt::Write, C: crate::data::FileCache>(
|
||||
&self,
|
||||
fmt: &mut U,
|
||||
cache: &C,
|
||||
config: &crate::RenderConfig,
|
||||
) -> super::Res {
|
||||
Renderable::<Compact>::render(&self.header(), fmt, cache, config)?;
|
||||
Renderable::<Compact>::render(&self.subtitles(), fmt, cache, config)?;
|
||||
Renderable::<Compact>::render(&self.markers(), fmt, cache, config)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderable<Compact> for Log {
|
||||
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, _: &C, _: &RenderConfig) -> Res {
|
||||
match self {
|
||||
Log::Compiled(_) => writeln!(fmt, "compiled"),
|
||||
Log::Checked(_) => writeln!(fmt, "checked"),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
74
crates/kind-report/src/report/mode/mod.rs
Normal file
74
crates/kind-report/src/report/mode/mod.rs
Normal file
@ -0,0 +1,74 @@
|
||||
use std::{fmt::Write, path::Path};
|
||||
|
||||
use refl::Id;
|
||||
|
||||
use crate::{data::{FileCache, Diagnostic, DiagnosticFrame, Log}, RenderConfig};
|
||||
use super::code::FileMarkers;
|
||||
|
||||
pub mod classic;
|
||||
pub mod compact;
|
||||
|
||||
// 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 {}
|
||||
|
||||
/// The enum of all of the modes so we can choose
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Mode {
|
||||
Classic,
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Renderable<T> for Box<dyn Diagnostic> where DiagnosticFrame: Renderable<T> {
|
||||
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, cache: &C, config: &RenderConfig) -> Res {
|
||||
Renderable::<T>::render(&self.to_diagnostic_frame(config), fmt, cache, config)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Report where Self : Renderable<Classic> + Renderable<Compact> {
|
||||
fn render<U: Write, C: FileCache>(&self, fmt: &mut U, cache: &C, config: &RenderConfig) -> Res {
|
||||
match config.mode {
|
||||
Mode::Classic => Renderable::<Classic>::render(self, fmt, cache, config),
|
||||
Mode::Compact => Renderable::<Compact>::render(self, fmt, cache, config),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Report for Box<dyn Diagnostic> {}
|
||||
impl Report for Log {}
|
@ -1,4 +1,4 @@
|
||||
use kind_report::data::{Color, Diagnostic, DiagnosticFrame, Marker, Severity};
|
||||
use kind_report::{data::{Color, Diagnostic, DiagnosticFrame, Marker, Severity}, RenderConfig};
|
||||
use kind_span::Range;
|
||||
|
||||
pub enum KdlDiagnostic {
|
||||
@ -20,7 +20,7 @@ impl Diagnostic for KdlDiagnostic {
|
||||
}
|
||||
}
|
||||
|
||||
fn to_diagnostic_frame(&self) -> kind_report::data::DiagnosticFrame {
|
||||
fn to_diagnostic_frame(&self, _: &RenderConfig) -> kind_report::data::DiagnosticFrame {
|
||||
match self {
|
||||
KdlDiagnostic::InvalidVarName(s, range) => DiagnosticFrame {
|
||||
code: 600,
|
||||
|
@ -1,6 +1,6 @@
|
||||
INFO Inspection.
|
||||
INFO Inspection
|
||||
|
||||
* Expected: U60
|
||||
* Hole: U60
|
||||
|
||||
|
||||
/--[suite/checker/Inspection.kind2:3:3]
|
||||
|
@ -1,14 +1,14 @@
|
||||
INFO Inspection.
|
||||
INFO Inspection
|
||||
|
||||
* Expected: U60
|
||||
* Hole: U60
|
||||
|
||||
* Context:
|
||||
* awoo : Type
|
||||
* awoo = U60
|
||||
* uuuhuuul : (List awoo)
|
||||
* uuuhuuul = (List.nil awoo)
|
||||
* ooooooo : (List U60)
|
||||
* ooooooo = uuuhuuul
|
||||
* Context
|
||||
* awoo : Type
|
||||
* awoo = U60
|
||||
* uuuhuuul : (List awoo)
|
||||
* uuuhuuul = (List.nil awoo)
|
||||
* ooooooo : (List U60)
|
||||
* ooooooo = uuuhuuul
|
||||
|
||||
/--[suite/checker/Subst.kind2:11:5]
|
||||
|
|
||||
|
@ -3,12 +3,12 @@
|
||||
* Got : ((x_1 : Type) -> (x_2 : Type) -> Type)
|
||||
* Expected : ((x_1 : t) -> (x_2 : t) -> t)
|
||||
|
||||
* Context:
|
||||
* t : Type
|
||||
* t_ : Type
|
||||
* t_ = t
|
||||
* magma : (Algebra.Magma Type)
|
||||
* assoc : ((a : _) -> (b : _) -> (c : _) -> (Equal _ (((Algebra.Magma.concat _ magma) (((Algebra.Magma.concat _ magma) a) b)) c) (((Algebra.Magma.concat _ magma) a) (((Algebra.Magma.concat _ magma) b) c))))
|
||||
* Context
|
||||
* t : Type
|
||||
* t_ : Type
|
||||
* t_ = t
|
||||
* magma : (Algebra.Magma Type)
|
||||
* assoc : ((a : _) -> (b : _) -> (c : _) -> (Equal _ (((Algebra.Magma.concat _ magma) (((Algebra.Magma.concat _ magma) a) b)) c) (((Algebra.Magma.concat _ magma) a) (((Algebra.Magma.concat _ magma) b) c))))
|
||||
|
||||
/--[suite/checker/fail/MismatchOne.kind2:32:69]
|
||||
|
|
||||
|
@ -3,8 +3,8 @@
|
||||
* Got : Type
|
||||
* Expected : U60
|
||||
|
||||
* Context:
|
||||
* a : U60
|
||||
* Context
|
||||
* a : U60
|
||||
|
||||
/--[suite/checker/fail/MismatchTwo.kind2:2:10]
|
||||
|
|
||||
|
@ -1,9 +1,9 @@
|
||||
INFO Inspection.
|
||||
INFO Inspection
|
||||
|
||||
* Expected: (Assert (Nat.count_layers n 1))
|
||||
* Hole: (Assert (Nat.count_layers n 1))
|
||||
|
||||
* Context:
|
||||
* n : Nat
|
||||
* Context
|
||||
* n : Nat
|
||||
|
||||
/--[suite/issues/checker/HvmReducesTooMuch.kind2:14:29]
|
||||
|
|
||||
|
@ -1,11 +1,11 @@
|
||||
INFO Inspection.
|
||||
INFO Inspection
|
||||
|
||||
* Expected: U60
|
||||
* Hole: U60
|
||||
|
||||
* Context:
|
||||
* n : (Pair U60 U60)
|
||||
* n.fst : U60
|
||||
* n.snd : U60
|
||||
* Context
|
||||
* n : (Pair U60 U60)
|
||||
* n.fst : U60
|
||||
* n.snd : U60
|
||||
|
||||
/--[suite/issues/checker/ISSUE-457.kind2:10:16]
|
||||
|
|
||||
@ -16,14 +16,14 @@
|
||||
11 | }
|
||||
|
||||
|
||||
INFO Inspection.
|
||||
INFO Inspection
|
||||
|
||||
* Expected: U60
|
||||
* Hole: U60
|
||||
|
||||
* Context:
|
||||
* n : (Pair U60 U60)
|
||||
* n.fst : U60
|
||||
* n.snd : U60
|
||||
* Context
|
||||
* n : (Pair U60 U60)
|
||||
* n.fst : U60
|
||||
* n.snd : U60
|
||||
|
||||
/--[suite/issues/checker/ISSUE-457.kind2:17:5]
|
||||
|
|
||||
@ -34,14 +34,14 @@
|
||||
18 |
|
||||
|
||||
|
||||
INFO Inspection.
|
||||
INFO Inspection
|
||||
|
||||
* Expected: U60
|
||||
* Hole: U60
|
||||
|
||||
* Context:
|
||||
* n : (Pair U60 U60)
|
||||
* fst : U60
|
||||
* snd : U60
|
||||
* Context
|
||||
* n : (Pair U60 U60)
|
||||
* fst : U60
|
||||
* snd : U60
|
||||
|
||||
/--[suite/issues/checker/ISSUE-457.kind2:22:5]
|
||||
|
|
||||
|
@ -1,14 +1,14 @@
|
||||
INFO Inspection.
|
||||
INFO Inspection
|
||||
|
||||
* Expected: U60
|
||||
* Hole: U60
|
||||
|
||||
* Context:
|
||||
* awoo : Type
|
||||
* awoo = U60
|
||||
* uuuhuuul : (List awoo)
|
||||
* uuuhuuul = (List.nil awoo)
|
||||
* ooooooo : (List U60)
|
||||
* ooooooo = uuuhuuul
|
||||
* Context
|
||||
* awoo : Type
|
||||
* awoo = U60
|
||||
* uuuhuuul : (List awoo)
|
||||
* uuuhuuul = (List.nil awoo)
|
||||
* ooooooo : (List U60)
|
||||
* ooooooo = uuuhuuul
|
||||
|
||||
/--[suite/issues/checker/ISSUE-461.kind2:11:5]
|
||||
|
|
||||
|
@ -3,10 +3,10 @@
|
||||
* Got : (Run n)
|
||||
* Expected : U60
|
||||
|
||||
* Context:
|
||||
* t1 : Type
|
||||
* n : Nat
|
||||
* f : (Run n)
|
||||
* Context
|
||||
* t1 : Type
|
||||
* n : Nat
|
||||
* f : (Run n)
|
||||
|
||||
/--[suite/issues/checker/TestWith.kind2:14:22]
|
||||
|
|
||||
|
@ -1,6 +1,6 @@
|
||||
INFO Inspection.
|
||||
INFO Inspection
|
||||
|
||||
* Expected: (Equal _ 2n 5n)
|
||||
* Hole: (Equal _ 2n 5n)
|
||||
|
||||
|
||||
/--[suite/issues/checker/U60ToNatDoesNotReduce.kind2:30:17]
|
||||
|
@ -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};
|
||||
@ -40,7 +40,7 @@ fn test_kind2(path: &Path, run: fn(&PathBuf, &mut Session) -> Option<String>) ->
|
||||
let res = run(&PathBuf::from(path), &mut session);
|
||||
|
||||
let diagnostics = tx.try_iter().collect::<Vec<Box<dyn Diagnostic>>>();
|
||||
let render = RenderConfig::ascii(2);
|
||||
let render = RenderConfig::ascii(2, false);
|
||||
|
||||
kind_report::check_if_colors_are_supported(true);
|
||||
|
||||
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user