1use super::{Diagnostic, DiagnosticLevel};
5use std::fmt::Write;
6
7pub mod colors {
9 pub const RESET: &str = "\x1b[0m";
10 pub const BOLD: &str = "\x1b[1m";
11 pub const DIM: &str = "\x1b[2m";
12 pub const UNDERLINE: &str = "\x1b[4m";
13
14 pub const RED: &str = "\x1b[31m";
16 pub const GREEN: &str = "\x1b[32m";
17 pub const YELLOW: &str = "\x1b[33m";
18 pub const BLUE: &str = "\x1b[34m";
19 pub const MAGENTA: &str = "\x1b[35m";
20 pub const CYAN: &str = "\x1b[36m";
21 pub const WHITE: &str = "\x1b[37m";
22
23 pub const BRIGHT_RED: &str = "\x1b[91m";
25 pub const BRIGHT_GREEN: &str = "\x1b[92m";
26 pub const BRIGHT_YELLOW: &str = "\x1b[93m";
27 pub const BRIGHT_BLUE: &str = "\x1b[94m";
28 pub const BRIGHT_MAGENTA: &str = "\x1b[95m";
29 pub const BRIGHT_CYAN: &str = "\x1b[96m";
30}
31
32pub struct ErrorStyle {
34 pub use_color: bool,
35 pub use_unicode: bool,
36 pub compact: bool,
37}
38
39impl Default for ErrorStyle {
40 fn default() -> Self {
41 Self {
42 use_color: true,
43 use_unicode: true,
44 compact: false,
45 }
46 }
47}
48
49impl ErrorStyle {
50 pub fn level_style(&self, level: DiagnosticLevel) -> (&'static str, String, &'static str) {
52 if !self.use_color {
53 return match level {
54 DiagnosticLevel::Error => ("error", String::new(), ""),
55 DiagnosticLevel::Warning => ("warning", String::new(), ""),
56 DiagnosticLevel::Info => ("info", String::new(), ""),
57 DiagnosticLevel::Help => ("help", String::new(), ""),
58 };
59 }
60
61 match level {
62 DiagnosticLevel::Error => (
63 "error",
64 format!("{}{}", colors::BOLD, colors::BRIGHT_RED),
65 colors::RESET,
66 ),
67 DiagnosticLevel::Warning => (
68 "warning",
69 format!("{}{}", colors::BOLD, colors::BRIGHT_YELLOW),
70 colors::RESET,
71 ),
72 DiagnosticLevel::Info => (
73 "info",
74 format!("{}{}", colors::BOLD, colors::BRIGHT_CYAN),
75 colors::RESET,
76 ),
77 DiagnosticLevel::Help => (
78 "help",
79 format!("{}{}", colors::BOLD, colors::BRIGHT_GREEN),
80 colors::RESET,
81 ),
82 }
83 }
84
85 pub fn path_style(&self) -> (String, &'static str) {
87 if self.use_color {
88 (format!("{}{}", colors::BOLD, colors::BLUE), colors::RESET)
89 } else {
90 (String::new(), "")
91 }
92 }
93
94 pub fn line_number_style(&self) -> (&'static str, &'static str) {
96 if self.use_color {
97 (colors::BLUE, colors::RESET)
98 } else {
99 ("", "")
100 }
101 }
102
103 pub fn error_style(&self) -> (String, &'static str) {
105 if self.use_color {
106 (
107 format!("{}{}", colors::BOLD, colors::BRIGHT_RED),
108 colors::RESET,
109 )
110 } else {
111 (String::new(), "")
112 }
113 }
114
115 pub fn note_style(&self) -> (String, &'static str) {
117 if self.use_color {
118 (
119 format!("{}{}", colors::BOLD, colors::BRIGHT_CYAN),
120 colors::RESET,
121 )
122 } else {
123 (String::new(), "")
124 }
125 }
126
127 pub fn suggestion_style(&self) -> (String, &'static str) {
129 if self.use_color {
130 (
131 format!("{}{}", colors::BOLD, colors::BRIGHT_GREEN),
132 colors::RESET,
133 )
134 } else {
135 (String::new(), "")
136 }
137 }
138
139 pub fn dim_style(&self) -> (&'static str, &'static str) {
141 if self.use_color {
142 (colors::DIM, colors::RESET)
143 } else {
144 ("", "")
145 }
146 }
147
148 pub fn get_chars(&self) -> DrawingChars {
150 if self.use_unicode {
151 DrawingChars::unicode()
152 } else {
153 DrawingChars::ascii()
154 }
155 }
156}
157
158pub struct DrawingChars {
160 pub vertical: &'static str,
161 pub horizontal: &'static str,
162 pub top_left: &'static str,
163 pub arrow: &'static str,
164 pub pointer_start: &'static str,
165 pub pointer_line: &'static str,
166}
167
168impl DrawingChars {
169 pub fn unicode() -> Self {
170 Self {
171 vertical: "│",
172 horizontal: "─",
173 top_left: "┌",
174 arrow: "→",
175 pointer_start: "^",
176 pointer_line: "─",
177 }
178 }
179
180 pub fn ascii() -> Self {
181 Self {
182 vertical: "|",
183 horizontal: "-",
184 top_left: "+",
185 arrow: "->",
186 pointer_start: "^",
187 pointer_line: "~",
188 }
189 }
190}
191
192pub fn format_diagnostic(diagnostic: &Diagnostic, style: &ErrorStyle) -> String {
194 let mut output = String::new();
195
196 let (level_text, level_start, level_end) = style.level_style(diagnostic.level);
198 write!(
199 &mut output,
200 "{}{}{}: {}{}{}",
201 level_start,
202 level_text,
203 level_end,
204 colors::BOLD,
205 diagnostic.message,
206 colors::RESET
207 )
208 .unwrap();
209
210 output
211}
212
213pub fn boxed_message(title: &str, content: &str, style: &ErrorStyle) -> String {
215 let chars = style.get_chars();
216 let width = content
217 .lines()
218 .map(|line| line.len())
219 .max()
220 .unwrap_or(0)
221 .max(title.len() + 4);
222
223 let mut output = String::new();
224
225 writeln!(
227 &mut output,
228 "{}{}",
229 chars.top_left,
230 chars.horizontal.repeat(width + 2)
231 )
232 .unwrap();
233
234 if !title.is_empty() {
236 let (suggestion_start, suggestion_end) = style.suggestion_style();
237 writeln!(
238 &mut output,
239 "{} {} {}{}",
240 chars.vertical, suggestion_start, title, suggestion_end
241 )
242 .unwrap();
243
244 writeln!(
246 &mut output,
247 "{}{}",
248 chars.vertical,
249 chars.horizontal.repeat(width + 2)
250 )
251 .unwrap();
252 }
253
254 for line in content.lines() {
256 writeln!(
257 &mut output,
258 "{} {:<width$} {}",
259 chars.vertical,
260 line,
261 chars.vertical,
262 width = width
263 )
264 .unwrap();
265 }
266
267 writeln!(
269 &mut output,
270 "{}{}",
271 chars.top_left,
272 chars.horizontal.repeat(width + 2)
273 )
274 .unwrap();
275
276 output
277}