use colored::*;
use crate::diagnostic::{Applicability, Diagnostic, DiagnosticCode, Label, LabelStyle, Suggestion};
use crate::style::*;
pub fn format_compact<C: DiagnosticCode>(d: &Diagnostic<C>, color: bool) -> String {
let mut out = String::new();
write_header(d, &mut out, color);
write_locations(d, &mut out, color);
write_label_messages(d, &mut out, color);
write_notes_help(d, &mut out, color);
write_suggestions(d, &mut out, color);
out.push('\n');
out
}
fn write_header<C: DiagnosticCode>(d: &Diagnostic<C>, out: &mut String, color: bool) {
out.push_str(&format!(
"{}[{}]: {}",
severity_word(d.severity, color),
code_word(d.severity, d.code.code(), color),
d.message,
));
if let Some(u) = d.code.url() {
out.push_str(&format!(" {}", paint(&format!("(see {u})"), color, |s| s.blue().italic())));
}
out.push('\n');
}
fn write_locations<C: DiagnosticCode>(d: &Diagnostic<C>, out: &mut String, color: bool) {
if d.labels.is_empty() {
return;
}
let arrow_s = arrow(color);
for file in distinct_files(&d.labels) {
let Some(anchor) = anchor_for_file(&d.labels, &file) else { continue };
let loc = if color {
format!(
"{}:{}:{}",
anchor.span.file.white().bold(),
anchor.span.line.to_string().white().bold(),
anchor.span.column.to_string().white().bold(),
)
} else {
format!("{}:{}:{}", anchor.span.file, anchor.span.line, anchor.span.column)
};
out.push_str(&format!(" {} {}\n", arrow_s, loc));
}
}
fn write_label_messages<C: DiagnosticCode>(d: &Diagnostic<C>, out: &mut String, color: bool) {
let eq = eq_sep(color);
for l in &d.labels {
if let Some(msg) = &l.message {
let kind = match l.style {
LabelStyle::Primary => "primary",
LabelStyle::Secondary => "note",
};
out.push_str(&format!(
" {} {}: {}\n",
eq,
paint_label(d.severity, l.style, kind, color),
paint_label(d.severity, l.style, msg, color),
));
}
if let Some(note) = &l.note {
out.push_str(&format!(" {}\n", paint(note, color, |s| s.cyan().italic())));
}
}
}
fn write_notes_help<C: DiagnosticCode>(d: &Diagnostic<C>, out: &mut String, color: bool) {
let eq = eq_sep(color);
for note in &d.notes {
out.push_str(&format!(" {} {}: {}\n", eq, meta_label("note", color), note));
}
if let Some(help) = &d.help {
out.push_str(&format!(" {} {}: {}\n", eq, meta_label("help", color), help));
}
}
fn write_suggestions<C: DiagnosticCode>(d: &Diagnostic<C>, out: &mut String, color: bool) {
if d.suggestions.is_empty() {
return;
}
let eq = eq_sep(color);
let help = meta_label("help", color);
for s in &d.suggestions {
let header = s.message.clone().unwrap_or_else(|| "try this:".to_string());
out.push_str(&format!(" {} {}: {}\n", eq, help, header));
for line in s.replacement.lines() {
out.push_str(&format!(" {}\n", paint(line, color, |s| s.green())));
}
write_applicability(out, s, color);
}
}
fn write_applicability(out: &mut String, s: &Suggestion, color: bool) {
let kind = match s.applicability {
Applicability::MachineApplicable => "auto-applicable",
Applicability::MaybeIncorrect => "review needed",
Applicability::HasPlaceholders => "has placeholders",
Applicability::Unspecified => return,
};
out.push_str(&format!(" ({})\n", paint(kind, color, |s| s.dimmed())));
}
fn distinct_files(labels: &[Label]) -> Vec<std::sync::Arc<str>> {
let mut files: Vec<std::sync::Arc<str>> = Vec::new();
for l in labels {
if !files.iter().any(|f| **f == *l.span.file) {
files.push(l.span.file.clone());
}
}
files
}
fn anchor_for_file<'a>(labels: &'a [Label], file: &str) -> Option<&'a Label> {
labels
.iter()
.filter(|l| *l.span.file == *file)
.find(|l| l.style == LabelStyle::Primary)
.or_else(|| labels.iter().find(|l| *l.span.file == *file))
}