1#![doc = include_str!("../readme.md")]
2#![warn(missing_docs)]
3
4mod display;
5mod draw;
6mod source;
7mod write;
8
9pub use crate::{
10 draw::{ColorGenerator, Fmt},
11 source::{FileCache, Line, Source},
12};
13use std::fmt::{Debug, Display, Formatter};
14pub use yansi::Color;
15
16use crate::display::*;
17use std::{
18 cmp::{Eq, PartialEq},
19 hash::Hash,
20 io::Write,
21 ops::Range,
22};
23use unicode_width::UnicodeWidthChar;
24
25#[derive(Copy, Clone, Eq, PartialEq, Hash)]
27pub struct FileID {
28 hash: u64,
29}
30
31impl Display for FileID {
32 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
33 write!(f, "FileID({})", self.hash)
34 }
35}
36
37impl FileID {
38 pub unsafe fn new(id: u64) -> Self {
40 Self { hash: id }
41 }
42 pub fn with_range(self, range: Range<usize>) -> FileSpan {
44 FileSpan { start: range.start, end: range.end, file: self }
45 }
46}
47
48#[derive(Copy, Clone, Eq, PartialEq, Hash)]
50pub struct FileSpan {
51 start: usize,
52 end: usize,
53 file: FileID,
54}
55
56impl Debug for FileID {
57 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
58 f.debug_struct("FileID").field("id", &self.hash).finish()
59 }
60}
61
62impl Debug for FileSpan {
63 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
64 f.debug_struct("FileSpan").field("start", &self.start).field("end", &self.end).field("file", &self.file).finish()
65 }
66}
67
68impl FileSpan {
69 pub unsafe fn new(start: usize, end: usize, file: FileID) -> Self {
71 Self { start, end, file }
72 }
73 pub fn get_range(&self) -> Range<usize> {
75 self.start..self.end
76 }
77 pub fn set_range(&mut self, range: Range<usize>) {
79 self.start = range.start;
80 self.end = range.end;
81 }
82 pub fn with_range(self, range: Range<usize>) -> Self {
84 Self { start: range.start, end: range.end, ..self }
85 }
86 pub fn get_file(&self) -> FileID {
88 self.file
89 }
90 pub fn set_file(&mut self, file: FileID) {
92 self.file = file;
93 }
94 pub fn with_file(self, file: FileID) -> Self {
96 Self { file, ..self }
97 }
98}
99
100pub trait Span {
102 type SourceId: PartialEq + ToOwned + ?Sized;
104
105 fn source(&self) -> &Self::SourceId;
107
108 fn start(&self) -> usize;
112
113 fn end(&self) -> usize;
119
120 fn len(&self) -> usize {
122 self.end().saturating_sub(self.start())
123 }
124
125 fn contains(&self, offset: usize) -> bool {
127 (self.start()..self.end()).contains(&offset)
128 }
129}
130
131impl Span for FileSpan {
132 type SourceId = FileID;
133
134 fn source(&self) -> &Self::SourceId {
135 &self.file
136 }
137
138 fn start(&self) -> usize {
139 self.start
140 }
141
142 fn end(&self) -> usize {
143 self.end
144 }
145}
146
147#[derive(Clone, Debug, Hash, PartialEq, Eq)]
149pub struct Label {
150 span: FileSpan,
151 msg: Option<String>,
152 color: Option<Color>,
153 order: i32,
154 priority: i32,
155}
156
157impl Label {
158 pub fn new(span: FileSpan) -> Self {
160 Self { span, msg: None, color: None, order: 0, priority: 0 }
161 }
162
163 pub fn with_message<M: ToString>(mut self, msg: M) -> Self {
165 self.msg = Some(msg.to_string());
166 self
167 }
168
169 pub fn with_color(mut self, color: Color) -> Self {
171 self.color = Some(color);
172 self
173 }
174
175 pub fn with_order(mut self, order: i32) -> Self {
186 self.order = order;
187 self
188 }
189
190 pub fn with_priority(mut self, priority: i32) -> Self {
200 self.priority = priority;
201 self
202 }
203}
204
205pub struct Report {
207 kind: Box<dyn ReportLevel>,
208 code: Option<usize>,
209 message: String,
210 note: Option<String>,
211 help: Option<String>,
212 location: (FileID, usize),
213 labels: Vec<Label>,
214 config: Config,
215}
216
217impl Report {
218 pub fn new<R>(kind: R, src_id: FileID, offset: usize) -> ReportBuilder
220 where
221 R: ReportLevel + 'static,
222 {
223 ReportBuilder {
224 kind: Box::new(kind),
225 code: None,
226 message: String::new(),
227 note: None,
228 help: None,
229 location: (src_id.into(), offset),
230 labels: Vec::new(),
231 config: Config::default(),
232 }
233 }
234
235 pub fn eprint(&self, cache: FileCache) -> std::io::Result<()> {
237 self.write(cache, std::io::stderr().lock())
238 }
239
240 pub fn print(&self, cache: FileCache) -> std::io::Result<()> {
245 self.write_for_stdout(cache, std::io::stdout().lock())
246 }
247}
248
249impl Debug for Report {
250 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
251 f.debug_struct("Report")
252 .field("kind", &self.kind)
253 .field("code", &self.code)
254 .field("msg", &self.message)
255 .field("note", &self.note)
256 .field("help", &self.help)
257 .field("config", &self.config)
258 .finish()
259 }
260}
261
262pub trait ReportLevel: Debug {
264 fn level(&self) -> u8;
266 fn get_color(&self) -> Color;
268}
269
270impl Debug for ReportKind {
271 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
272 match self {
273 ReportKind::Error => f.write_str("ERROR"),
274 ReportKind::Alert => f.write_str("ALERT"),
275 ReportKind::Trace => f.write_str("TRACE"),
276 ReportKind::Blame => f.write_str("BLAME"),
277 ReportKind::Fatal => f.write_str("FATAL"),
278 }
279 }
280}
281
282impl ReportLevel for ReportKind {
283 fn level(&self) -> u8 {
284 match self {
285 ReportKind::Trace => 0,
286 ReportKind::Blame => 150,
287 ReportKind::Alert => 200,
288 ReportKind::Error => 250,
289 ReportKind::Fatal => 255,
290 }
291 }
292
293 fn get_color(&self) -> Color {
294 match self {
295 ReportKind::Trace => Color::Cyan,
296 ReportKind::Blame => Color::Green,
297 ReportKind::Alert => Color::Yellow,
298 ReportKind::Error => Color::Red,
299 ReportKind::Fatal => Color::Magenta,
300 }
301 }
302}
303
304#[derive(Copy, Clone, PartialEq, Eq)]
313pub enum ReportKind {
314 Trace,
316 Blame,
318 Alert,
321 Error,
324 Fatal,
326}
327
328pub struct ReportBuilder {
330 kind: Box<dyn ReportLevel>,
331 code: Option<usize>,
332 message: String,
333 note: Option<String>,
334 help: Option<String>,
335 location: (FileID, usize),
336 labels: Vec<Label>,
337 config: Config,
338}
339
340impl ReportBuilder {
341 pub fn set_code(&mut self, code: Option<usize>) {
343 self.code = code;
344 }
345 pub fn with_code(mut self, code: usize) -> Self {
347 self.set_code(Some(code));
348 self
349 }
350
351 pub fn set_message<M: ToString>(&mut self, message: M) {
353 self.message = message.to_string();
354 }
355
356 pub fn with_message<M: ToString>(mut self, message: M) -> Self {
358 self.message = message.to_string();
359 self
360 }
361
362 pub fn set_note<N: ToString>(&mut self, note: N) {
364 self.note = Some(note.to_string());
365 }
366
367 pub fn with_note<N: ToString>(mut self, note: N) -> Self {
369 self.set_note(note);
370 self
371 }
372
373 pub fn set_help<N: ToString>(&mut self, note: N) {
375 self.help = Some(note.to_string());
376 }
377
378 pub fn with_help<N: ToString>(mut self, note: N) -> Self {
380 self.set_help(note);
381 self
382 }
383
384 pub fn add_label(&mut self, label: Label) {
386 self.add_labels(std::iter::once(label));
387 }
388
389 pub fn add_labels<L: IntoIterator<Item = Label>>(&mut self, labels: L) {
391 let config = &self.config; self.labels.extend(labels.into_iter().map(|mut label| {
393 label.color = config.filter_color(label.color);
394 label
395 }));
396 }
397
398 pub fn with_label(mut self, label: Label) -> Self {
400 self.add_label(label);
401 self
402 }
403
404 pub fn with_labels<L: IntoIterator<Item = Label>>(mut self, labels: L) -> Self {
406 self.add_labels(labels);
407 self
408 }
409
410 pub fn with_config(mut self, config: Config) -> Self {
412 self.config = config;
413 self
414 }
415
416 pub fn finish(self) -> Report {
418 Report {
419 kind: self.kind,
420 code: self.code,
421 message: self.message,
422 note: self.note,
423 help: self.help,
424 location: self.location,
425 labels: self.labels,
426 config: self.config,
427 }
428 }
429}
430
431impl Debug for ReportBuilder {
432 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
433 f.debug_struct("ReportBuilder")
434 .field("kind", &self.kind)
435 .field("code", &self.code)
436 .field("msg", &self.message)
437 .field("note", &self.note)
438 .field("help", &self.help)
439 .field("config", &self.config)
440 .finish()
441 }
442}
443
444#[derive(Copy, Clone, Debug, PartialEq, Eq)]
446pub enum LabelAttach {
447 Start,
449 Middle,
451 End,
453}
454
455#[derive(Copy, Clone, Debug, PartialEq, Eq)]
457pub enum CharSet {
458 Unicode,
460 Ascii,
462}
463
464#[derive(Copy, Clone, Debug, PartialEq, Eq)]
466pub struct Config {
467 cross_gap: bool,
468 label_attach: LabelAttach,
469 compact: bool,
470 underlines: bool,
471 multiline_arrows: bool,
472 color: bool,
473 tab_width: usize,
474 char_set: CharSet,
475}
476
477impl Config {
478 pub fn with_cross_gap(mut self, cross_gap: bool) -> Self {
484 self.cross_gap = cross_gap;
485 self
486 }
487 pub fn with_label_attach(mut self, label_attach: LabelAttach) -> Self {
491 self.label_attach = label_attach;
492 self
493 }
494 pub fn with_compact(mut self, compact: bool) -> Self {
498 self.compact = compact;
499 self
500 }
501 pub fn with_underlines(mut self, underlines: bool) -> Self {
505 self.underlines = underlines;
506 self
507 }
508 pub fn with_multiline_arrows(mut self, multiline_arrows: bool) -> Self {
512 self.multiline_arrows = multiline_arrows;
513 self
514 }
515 pub fn with_color(mut self, color: bool) -> Self {
519 self.color = color;
520 self
521 }
522 pub fn with_tab_width(mut self, tab_width: usize) -> Self {
526 self.tab_width = tab_width;
527 self
528 }
529 pub fn with_char_set(mut self, char_set: CharSet) -> Self {
533 self.char_set = char_set;
534 self
535 }
536
537 fn margin_color(&self) -> Option<Color> {
538 Some(Color::Fixed(246)).filter(|_| self.color)
539 }
540 fn skipped_margin_color(&self) -> Option<Color> {
541 Some(Color::Fixed(240)).filter(|_| self.color)
542 }
543 fn unimportant_color(&self) -> Option<Color> {
544 Some(Color::Fixed(249)).filter(|_| self.color)
545 }
546 fn note_color(&self) -> Option<Color> {
547 Some(Color::Fixed(115)).filter(|_| self.color)
548 }
549 fn filter_color(&self, color: Option<Color>) -> Option<Color> {
550 color.filter(|_| self.color)
551 }
552
553 fn char_width(&self, c: char, col: usize) -> (char, usize) {
555 match c {
556 '\t' => {
557 let tab_end = (col / self.tab_width + 1) * self.tab_width;
559 (' ', tab_end - col)
560 }
561 c if c.is_whitespace() => (' ', 1),
562 _ => (c, c.width().unwrap_or(1)),
563 }
564 }
565}
566
567impl Default for Config {
568 fn default() -> Self {
569 Self {
570 cross_gap: true,
571 label_attach: LabelAttach::Middle,
572 compact: false,
573 underlines: true,
574 multiline_arrows: true,
575 color: true,
576 tab_width: 4,
577 char_set: CharSet::Unicode,
578 }
579 }
580}