1use aver::diagnostics::{Diagnostic, Severity};
13use colored::Colorize;
14use std::fmt::Write;
15
16pub fn render_tty(d: &Diagnostic, verbose: bool) -> String {
19 let mut out = String::new();
20
21 let tag = match d.severity {
23 Severity::Error => "error",
24 Severity::Warning => "warning",
25 Severity::Fail => "fail",
26 Severity::Hint => "hint",
27 };
28 let header_text = format!("{}[{}]: {}", tag, d.slug, d.summary);
29 let header = match d.severity {
30 Severity::Error | Severity::Fail => header_text.red().bold().to_string(),
31 Severity::Warning => header_text.yellow().bold().to_string(),
32 Severity::Hint => header_text.cyan().bold().to_string(),
33 };
34 let _ = writeln!(out, "{}", header);
35
36 let at_label = "at:".blue().to_string();
38 let _ = writeln!(
39 out,
40 " {} {}:{}:{}",
41 at_label, d.span.file, d.span.line, d.span.col
42 );
43
44 if let Some(ref fn_name) = d.fn_name {
46 let key = "in-fn:".blue().to_string();
47 let _ = writeln!(out, " {} {}", key, fn_name);
48 }
49
50 if verbose && let Some(ref intent) = d.intent {
52 let key = "intent:".blue().to_string();
53 let _ = writeln!(out, " {} {}", key, intent.dimmed());
54 }
55
56 let is_error = d.is_error();
57
58 if is_error && let Some(ref conflict) = d.conflict {
60 let key = "conflict:".blue().to_string();
61 let _ = writeln!(out, " {} {}", key, conflict);
62 }
63
64 let field_limit = if verbose {
66 d.fields.len()
67 } else if is_error {
68 4
69 } else {
70 2
71 };
72 for (key, value) in d.fields.iter().take(field_limit) {
73 let colored_key = format!("{}:", key).blue().to_string();
74 let _ = writeln!(out, " {} {}", colored_key, value);
75 }
76
77 if let Some(ref repair) = d.repair.primary {
79 let key = "repair:".blue().to_string();
80 let _ = writeln!(out, " {} {}", key, repair.cyan());
81 }
82
83 if verbose {
85 for alt in &d.repair.alternatives {
86 let key = "repair.alt:".blue().to_string();
87 let _ = writeln!(out, " {} {}", key, alt.cyan());
88 }
89 }
90
91 if verbose && let Some(ref example) = d.repair.example {
93 let key = "repair.example:".blue().to_string();
94 let _ = writeln!(out, " {} {}", key, example.cyan());
95 }
96
97 let skip_snippet = matches!(
99 d.slug,
100 "missing-verify" | "verify-effectful" | "missing-description"
101 );
102 let show_source = (is_error || verbose) && !skip_snippet;
103 let has_source = d.regions.iter().any(|r| !r.source_lines.is_empty());
104 if show_source && has_source {
105 let max_num = d
106 .regions
107 .iter()
108 .flat_map(|r| r.source_lines.iter().map(|sl| sl.line_num))
109 .max()
110 .unwrap_or(0);
111 let gutter_width = format!("{}", max_num).len();
112 let gutter_pad: String = " ".repeat(gutter_width);
113
114 let _ = writeln!(out, " {} {}", gutter_pad, "|".blue());
115
116 let mut last_emitted: Option<usize> = None;
117
118 for region in &d.regions {
119 if let Some(first_sl) = region.source_lines.first()
120 && let Some(last) = last_emitted
121 && first_sl.line_num > last + 1
122 {
123 let _ = writeln!(out, " {}", "...".blue());
124 }
125
126 for sl in ®ion.source_lines {
127 if let Some(last) = last_emitted
128 && sl.line_num <= last
129 {
130 continue;
131 }
132 let num_str = format!("{:>width$}", sl.line_num, width = gutter_width);
133 let _ = writeln!(out, " {} {} {}", num_str.dimmed(), "|".blue(), sl.text);
134 last_emitted = Some(sl.line_num);
135 }
136
137 if let Some(ref ul) = region.underline {
138 let pad: String = " ".repeat(ul.col.saturating_sub(1));
139 let carets: String = "^".repeat(ul.len.max(1));
140 let colored_carets = match d.severity {
141 Severity::Error | Severity::Fail => carets.red().to_string(),
142 Severity::Warning => carets.yellow().to_string(),
143 Severity::Hint => carets.cyan().to_string(),
144 };
145 let _ = writeln!(
146 out,
147 " {} {} {}{} {}",
148 gutter_pad,
149 "|".blue(),
150 pad,
151 colored_carets,
152 ul.label.dimmed()
153 );
154 }
155 }
156 }
157
158 out
159}