use aver::diagnostics::{Diagnostic, Severity};
use colored::Colorize;
use std::fmt::Write;
pub fn render_tty(d: &Diagnostic, verbose: bool) -> String {
let mut out = String::new();
let tag = match d.severity {
Severity::Error => "error",
Severity::Warning => "warning",
Severity::Fail => "fail",
Severity::Hint => "hint",
};
let header_text = format!("{}[{}]: {}", tag, d.slug, d.summary);
let header = match d.severity {
Severity::Error | Severity::Fail => header_text.red().bold().to_string(),
Severity::Warning => header_text.yellow().bold().to_string(),
Severity::Hint => header_text.cyan().bold().to_string(),
};
let _ = writeln!(out, "{}", header);
let at_label = "at:".blue().to_string();
let _ = writeln!(
out,
" {} {}:{}:{}",
at_label, d.span.file, d.span.line, d.span.col
);
if let Some(ref fn_name) = d.fn_name {
let key = "in-fn:".blue().to_string();
let _ = writeln!(out, " {} {}", key, fn_name);
}
if verbose && let Some(ref intent) = d.intent {
let key = "intent:".blue().to_string();
let _ = writeln!(out, " {} {}", key, intent.dimmed());
}
let is_error = d.is_error();
if is_error && let Some(ref conflict) = d.conflict {
let key = "conflict:".blue().to_string();
let _ = writeln!(out, " {} {}", key, conflict);
}
let field_limit = if verbose {
d.fields.len()
} else if is_error {
4
} else {
2
};
for (key, value) in d.fields.iter().take(field_limit) {
let colored_key = format!("{}:", key).blue().to_string();
let _ = writeln!(out, " {} {}", colored_key, value);
}
if let Some(ref repair) = d.repair.primary {
let key = "repair:".blue().to_string();
let _ = writeln!(out, " {} {}", key, repair.cyan());
}
if verbose {
for alt in &d.repair.alternatives {
let key = "repair.alt:".blue().to_string();
let _ = writeln!(out, " {} {}", key, alt.cyan());
}
}
if verbose && let Some(ref example) = d.repair.example {
let key = "repair.example:".blue().to_string();
let _ = writeln!(out, " {} {}", key, example.cyan());
}
let skip_snippet = matches!(
d.slug,
"missing-verify" | "verify-effectful" | "missing-description"
);
let show_source = (is_error || verbose) && !skip_snippet;
let has_source = d.regions.iter().any(|r| !r.source_lines.is_empty());
if show_source && has_source {
let max_num = d
.regions
.iter()
.flat_map(|r| r.source_lines.iter().map(|sl| sl.line_num))
.max()
.unwrap_or(0);
let gutter_width = format!("{}", max_num).len();
let gutter_pad: String = " ".repeat(gutter_width);
let _ = writeln!(out, " {} {}", gutter_pad, "|".blue());
let mut last_emitted: Option<usize> = None;
for region in &d.regions {
if let Some(first_sl) = region.source_lines.first()
&& let Some(last) = last_emitted
&& first_sl.line_num > last + 1
{
let _ = writeln!(out, " {}", "...".blue());
}
for sl in ®ion.source_lines {
if let Some(last) = last_emitted
&& sl.line_num <= last
{
continue;
}
let num_str = format!("{:>width$}", sl.line_num, width = gutter_width);
let _ = writeln!(out, " {} {} {}", num_str.dimmed(), "|".blue(), sl.text);
last_emitted = Some(sl.line_num);
}
if let Some(ref ul) = region.underline {
let pad: String = " ".repeat(ul.col.saturating_sub(1));
let carets: String = "^".repeat(ul.len.max(1));
let colored_carets = match d.severity {
Severity::Error | Severity::Fail => carets.red().to_string(),
Severity::Warning => carets.yellow().to_string(),
Severity::Hint => carets.cyan().to_string(),
};
let _ = writeln!(
out,
" {} {} {}{} {}",
gutter_pad,
"|".blue(),
pad,
colored_carets,
ul.label.dimmed()
);
}
}
}
out
}