1use alloc::string::String;
2use core::ops::Range;
3
4use crate::diagnostic::{LabelStyle, Severity};
5use crate::files::{Error, Location};
6use crate::term::{Chars, Config};
7
8#[cfg(feature = "std")]
9pub use std::io::Write as GeneralWrite;
10
11#[cfg(feature = "std")]
12pub type GeneralWriteResult = std::io::Result<()>;
13
14#[cfg(not(feature = "std"))]
15pub use core::fmt::Write as GeneralWrite;
16
17#[cfg(not(feature = "std"))]
18pub use core::fmt::Result as GeneralWriteResult;
19
20pub trait WriteStyle: GeneralWrite {
28 fn set_header(&mut self, severity: Severity) -> GeneralWriteResult;
29
30 fn set_header_message(&mut self) -> GeneralWriteResult;
31
32 fn set_line_number(&mut self) -> GeneralWriteResult;
33
34 fn set_note_bullet(&mut self) -> GeneralWriteResult;
35
36 fn set_source_border(&mut self) -> GeneralWriteResult;
37
38 fn set_label(&mut self, severity: Severity, label_style: LabelStyle) -> GeneralWriteResult;
39
40 fn reset(&mut self) -> GeneralWriteResult;
41}
42
43pub struct PlainWriter<W: GeneralWrite> {
46 w: W,
47}
48
49impl<W: GeneralWrite> PlainWriter<W> {
50 pub fn new(writer: W) -> Self {
51 Self { w: writer }
52 }
53}
54
55#[cfg(feature = "std")]
56impl<W: std::io::Write> std::io::Write for PlainWriter<W> {
57 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
58 self.w.write(buf)
59 }
60
61 fn flush(&mut self) -> std::io::Result<()> {
62 self.w.flush()
63 }
64}
65
66#[cfg(not(feature = "std"))]
67impl<W: core::fmt::Write> core::fmt::Write for PlainWriter<W> {
68 fn write_str(&mut self, s: &str) -> core::fmt::Result {
69 self.w.write_str(s)
70 }
71
72 fn write_char(&mut self, c: char) -> core::fmt::Result {
73 self.w.write_char(c)
74 }
75
76 fn write_fmt(&mut self, args: core::fmt::Arguments<'_>) -> core::fmt::Result {
77 self.w.write_fmt(args)
78 }
79}
80
81impl<W: GeneralWrite> WriteStyle for PlainWriter<W> {
82 fn set_header(&mut self, _severity: Severity) -> GeneralWriteResult {
83 Ok(())
84 }
85
86 fn set_header_message(&mut self) -> GeneralWriteResult {
87 Ok(())
88 }
89
90 fn set_line_number(&mut self) -> GeneralWriteResult {
91 Ok(())
92 }
93
94 fn set_note_bullet(&mut self) -> GeneralWriteResult {
95 Ok(())
96 }
97
98 fn set_source_border(&mut self) -> GeneralWriteResult {
99 Ok(())
100 }
101
102 fn set_label(&mut self, _severity: Severity, _label_style: LabelStyle) -> GeneralWriteResult {
103 Ok(())
104 }
105
106 fn reset(&mut self) -> GeneralWriteResult {
107 Ok(())
108 }
109}
110
111pub(crate) struct WriteStyleByRef<'a, W: ?Sized> {
112 w: &'a mut W,
113}
114
115impl<'a, W> WriteStyleByRef<'a, W>
116where
117 W: ?Sized,
118{
119 pub fn new(writer: &'a mut W) -> Self {
120 Self { w: writer }
121 }
122}
123
124#[cfg(feature = "std")]
125impl<'a, W> std::io::Write for WriteStyleByRef<'a, W>
126where
127 W: std::io::Write + ?Sized,
128{
129 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
130 self.w.write(buf)
131 }
132
133 fn flush(&mut self) -> std::io::Result<()> {
134 self.w.flush()
135 }
136}
137
138#[cfg(not(feature = "std"))]
139impl<'a, W> core::fmt::Write for WriteStyleByRef<'a, W>
140where
141 W: core::fmt::Write + ?Sized,
142{
143 fn write_str(&mut self, s: &str) -> core::fmt::Result {
144 self.w.write_str(s)
145 }
146
147 fn write_char(&mut self, c: char) -> core::fmt::Result {
148 self.w.write_char(c)
149 }
150
151 fn write_fmt(&mut self, args: core::fmt::Arguments<'_>) -> core::fmt::Result {
152 self.w.write_fmt(args)
153 }
154}
155
156impl<'a, W> WriteStyle for WriteStyleByRef<'a, W>
157where
158 W: WriteStyle + ?Sized,
159{
160 fn set_header(&mut self, severity: Severity) -> GeneralWriteResult {
161 self.w.set_header(severity)
162 }
163
164 fn set_header_message(&mut self) -> GeneralWriteResult {
165 self.w.set_header_message()
166 }
167
168 fn set_line_number(&mut self) -> GeneralWriteResult {
169 self.w.set_line_number()
170 }
171
172 fn set_note_bullet(&mut self) -> GeneralWriteResult {
173 self.w.set_note_bullet()
174 }
175
176 fn set_source_border(&mut self) -> GeneralWriteResult {
177 self.w.set_source_border()
178 }
179
180 fn set_label(&mut self, severity: Severity, label_style: LabelStyle) -> GeneralWriteResult {
181 self.w.set_label(severity, label_style)
182 }
183
184 fn reset(&mut self) -> GeneralWriteResult {
185 self.w.reset()
186 }
187}
188
189pub struct Locus {
191 pub name: String,
193 pub location: Location,
195}
196
197pub type SingleLabel<'diagnostic> = (LabelStyle, Range<usize>, &'diagnostic str);
203
204pub enum MultiLabel<'diagnostic> {
208 Top(usize),
222 Left,
228 Bottom(usize, &'diagnostic str),
235}
236
237#[derive(Copy, Clone)]
238enum VerticalBound {
239 Top,
240 Bottom,
241}
242
243type Underline = (LabelStyle, VerticalBound);
244
245pub struct Renderer<'writer, 'config> {
292 writer: &'writer mut dyn WriteStyle,
293 config: &'config Config,
294}
295
296impl<'writer, 'config> Renderer<'writer, 'config> {
297 pub fn new(
299 writer: &'writer mut dyn WriteStyle,
300 config: &'config Config,
301 ) -> Renderer<'writer, 'config> {
302 Renderer { writer, config }
303 }
304
305 fn chars(&self) -> &'config Chars {
306 &self.config.chars
307 }
308
309 pub fn render_header(
315 &mut self,
316 locus: Option<&Locus>,
317 severity: Severity,
318 code: Option<&str>,
319 message: &str,
320 ) -> Result<(), Error> {
321 if let Some(locus) = locus {
327 self.snippet_locus(locus)?;
328 write!(self, ": ")?;
329 }
330
331 self.set_header(severity)?;
337 match severity {
338 Severity::Bug => write!(self, "bug")?,
339 Severity::Error => write!(self, "error")?,
340 Severity::Warning => write!(self, "warning")?,
341 Severity::Help => write!(self, "help")?,
342 Severity::Note => write!(self, "note")?,
343 }
344
345 if let Some(code) = &code.filter(|code| !code.is_empty()) {
351 write!(self, "[{code}]")?;
352 }
353
354 self.set_header_message()?;
360 write!(self, ": {message}")?;
361 self.reset()?;
362
363 writeln!(self)?;
364
365 Ok(())
366 }
367
368 pub fn render_empty(&mut self) -> Result<(), Error> {
370 writeln!(self)?;
371 Ok(())
372 }
373
374 pub fn render_snippet_start(
380 &mut self,
381 outer_padding: usize,
382 locus: &Locus,
383 ) -> Result<(), Error> {
384 self.outer_gutter(outer_padding)?;
385
386 self.set_source_border()?;
387 write!(self, "{}", self.chars().snippet_start)?;
388 self.reset()?;
389
390 write!(self, " ")?;
391 self.snippet_locus(locus)?;
392
393 writeln!(self)?;
394
395 Ok(())
396 }
397
398 #[allow(clippy::too_many_arguments)]
405 pub fn render_snippet_source(
406 &mut self,
407 outer_padding: usize,
408 line_number: usize,
409 source: &str,
410 severity: Severity,
411 single_labels: &[SingleLabel<'_>],
412 num_multi_labels: usize,
413 multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)],
414 ) -> Result<(), Error> {
415 let source = source.trim_end_matches(['\n', '\r', '\0'].as_ref());
418
419 {
425 self.outer_gutter_number(line_number, outer_padding)?;
427 self.border_left()?;
428
429 let mut multi_labels_iter = multi_labels.iter().peekable();
431 for label_column in 0..num_multi_labels {
432 match multi_labels_iter.peek() {
433 Some((label_index, label_style, label)) if *label_index == label_column => {
434 match label {
435 MultiLabel::Top(start)
436 if *start <= source.len() - source.trim_start().len() =>
437 {
438 self.label_multi_top_left(severity, *label_style)?;
439 }
440 MultiLabel::Top(..) => self.inner_gutter_space()?,
441 MultiLabel::Left | MultiLabel::Bottom(..) => {
442 self.label_multi_left(severity, *label_style, None)?;
443 }
444 }
445 multi_labels_iter.next();
446 }
447 Some((_, _, _)) | None => self.inner_gutter_space()?,
448 }
449 }
450
451 write!(self, " ")?;
453 let mut in_primary = false;
454 for (metrics, ch) in self.char_metrics(source.char_indices()) {
455 let column_range = metrics.byte_index..(metrics.byte_index + ch.len_utf8());
456
457 let is_primary = single_labels.iter().any(|(ls, range, _)| {
459 *ls == LabelStyle::Primary && is_overlapping(range, &column_range)
460 }) || multi_labels.iter().any(|(_, ls, label)| {
461 *ls == LabelStyle::Primary
462 && match label {
463 MultiLabel::Top(start) => column_range.start >= *start,
464 MultiLabel::Left => true,
465 MultiLabel::Bottom(start, _) => column_range.end <= *start,
466 }
467 });
468
469 if is_primary && !in_primary {
471 self.set_label(severity, LabelStyle::Primary)?;
472 in_primary = true;
473 } else if !is_primary && in_primary {
474 self.reset()?;
475 in_primary = false;
476 }
477
478 match ch {
479 '\t' => (0..metrics.unicode_width).try_for_each(|_| write!(self, " "))?,
480 _ => write!(self, "{ch}")?,
481 }
482 }
483 if in_primary {
484 self.reset()?;
485 }
486 writeln!(self)?;
487 }
488
489 if !single_labels.is_empty() {
499 let mut num_messages = 0;
520 let mut max_label_start = 0;
528 let mut max_label_end = 0;
536 let mut trailing_label = None;
542
543 for (label_index, label) in single_labels.iter().enumerate() {
544 let (_, range, message) = label;
545 if !message.is_empty() {
546 num_messages += 1;
547 }
548 max_label_start = core::cmp::max(max_label_start, range.start);
549 max_label_end = core::cmp::max(max_label_end, range.end);
550 if range.end == max_label_end {
552 if message.is_empty() {
553 trailing_label = None;
554 } else {
555 trailing_label = Some((label_index, label));
556 }
557 }
558 }
559 if let Some((trailing_label_index, (_, trailing_range, _))) = trailing_label {
560 if single_labels
563 .iter()
564 .enumerate()
565 .filter(|(label_index, _)| *label_index != trailing_label_index)
566 .any(|(_, (_, range, _))| is_overlapping(trailing_range, range))
567 {
568 trailing_label = None;
571 }
572 }
573
574 self.outer_gutter(outer_padding)?;
580 self.border_left()?;
581 self.inner_gutter(severity, num_multi_labels, multi_labels)?;
582 write!(self, " ")?;
583
584 let mut previous_label_style = None;
585 let placeholder_metrics = Metrics {
586 byte_index: source.len(),
587 unicode_width: 1,
588 };
589 for (metrics, ch) in self
590 .char_metrics(source.char_indices())
591 .chain(core::iter::once((placeholder_metrics, '\0')))
599 {
600 let column_range = metrics.byte_index..(metrics.byte_index + ch.len_utf8());
602 let current_label_style = single_labels
603 .iter()
604 .filter(|(_, range, _)| is_overlapping(range, &column_range))
605 .map(|(label_style, _, _)| *label_style)
606 .max_by_key(label_priority_key);
607
608 if previous_label_style != current_label_style {
610 match current_label_style {
611 None => {
612 self.reset()?;
613 }
614 Some(label_style) => {
615 self.set_label(severity, label_style)?;
616 }
617 }
618 }
619
620 let caret_ch = match current_label_style {
621 Some(LabelStyle::Primary) => Some(self.chars().single_primary_caret),
622 Some(LabelStyle::Secondary) => Some(self.chars().single_secondary_caret),
623 None if metrics.byte_index < max_label_end => Some(' '),
625 None => None,
626 };
627 if let Some(caret_ch) = caret_ch {
628 (0..metrics.unicode_width).try_for_each(|_| write!(self, "{caret_ch}",))?;
630 }
631
632 previous_label_style = current_label_style;
633 }
634 if previous_label_style.is_some() {
636 self.reset()?;
637 }
638 if let Some((_, (label_style, _, message))) = trailing_label {
640 write!(self, " ")?;
641 self.set_label(severity, *label_style)?;
642 write!(self, "{message}",)?;
643 self.reset()?;
644 }
645 writeln!(self)?;
646
647 if num_messages > trailing_label.iter().count() {
656 self.outer_gutter(outer_padding)?;
662 self.border_left()?;
663 self.inner_gutter(severity, num_multi_labels, multi_labels)?;
664 write!(self, " ")?;
665 self.caret_pointers(
666 severity,
667 max_label_start,
668 single_labels,
669 trailing_label,
670 source.char_indices(),
671 )?;
672 writeln!(self)?;
673
674 for (label_style, range, message) in
682 hanging_labels(single_labels, trailing_label).rev()
683 {
684 self.outer_gutter(outer_padding)?;
685 self.border_left()?;
686 self.inner_gutter(severity, num_multi_labels, multi_labels)?;
687 write!(self, " ")?;
688 self.caret_pointers(
689 severity,
690 max_label_start,
691 single_labels,
692 trailing_label,
693 source
694 .char_indices()
695 .take_while(|(byte_index, _)| *byte_index < range.start),
696 )?;
697 self.set_label(severity, *label_style)?;
698 write!(self, "{message}",)?;
699 self.reset()?;
700 writeln!(self)?;
701 }
702 }
703 }
704
705 for (multi_label_index, (_, label_style, label)) in multi_labels.iter().enumerate() {
712 let (label_style, range, bottom_message) = match label {
713 MultiLabel::Left => continue, MultiLabel::Top(start) if *start <= source.len() - source.trim_start().len() => {
716 continue
717 }
718 MultiLabel::Top(range) => (*label_style, range, None),
719 MultiLabel::Bottom(range, message) => (*label_style, range, Some(message)),
720 };
721
722 self.outer_gutter(outer_padding)?;
723 self.border_left()?;
724
725 let mut underline = None;
731 let mut multi_labels_iter = multi_labels.iter().enumerate().peekable();
732 for label_column in 0..num_multi_labels {
733 match multi_labels_iter.peek() {
734 Some((i, (label_index, ls, label))) if *label_index == label_column => {
735 match label {
736 MultiLabel::Left => {
737 self.label_multi_left(severity, *ls, underline.map(|(s, _)| s))?;
738 }
739 MultiLabel::Top(..) if multi_label_index > *i => {
740 self.label_multi_left(severity, *ls, underline.map(|(s, _)| s))?;
741 }
742 MultiLabel::Bottom(..) if multi_label_index < *i => {
743 self.label_multi_left(severity, *ls, underline.map(|(s, _)| s))?;
744 }
745 MultiLabel::Top(..) if multi_label_index == *i => {
746 underline = Some((*ls, VerticalBound::Top));
747 self.label_multi_top_left(severity, label_style)?;
748 }
749 MultiLabel::Bottom(..) if multi_label_index == *i => {
750 underline = Some((*ls, VerticalBound::Bottom));
751 self.label_multi_bottom_left(severity, label_style)?;
752 }
753 MultiLabel::Top(..) | MultiLabel::Bottom(..) => {
754 self.inner_gutter_column(severity, underline)?;
755 }
756 }
757 multi_labels_iter.next();
758 }
759 Some((_, _)) | None => self.inner_gutter_column(severity, underline)?,
760 }
761 }
762
763 match bottom_message {
765 None => self.label_multi_top_caret(severity, label_style, source, *range)?,
766 Some(message) => {
767 self.label_multi_bottom_caret(severity, label_style, source, *range, message)?;
768 }
769 }
770 }
771
772 Ok(())
773 }
774
775 pub fn render_snippet_empty(
781 &mut self,
782 outer_padding: usize,
783 severity: Severity,
784 num_multi_labels: usize,
785 multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)],
786 ) -> Result<(), Error> {
787 self.outer_gutter(outer_padding)?;
788 self.border_left()?;
789 self.inner_gutter(severity, num_multi_labels, multi_labels)?;
790 writeln!(self)?;
791 Ok(())
792 }
793
794 pub fn render_snippet_break(
800 &mut self,
801 outer_padding: usize,
802 severity: Severity,
803 num_multi_labels: usize,
804 multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)],
805 ) -> Result<(), Error> {
806 self.outer_gutter(outer_padding)?;
807 self.border_left_break()?;
808 self.inner_gutter(severity, num_multi_labels, multi_labels)?;
809 writeln!(self)?;
810 Ok(())
811 }
812
813 pub fn render_snippet_note(
820 &mut self,
821 outer_padding: usize,
822 message: &str,
823 ) -> Result<(), Error> {
824 for (note_line_index, line) in message.lines().enumerate() {
825 self.outer_gutter(outer_padding)?;
826 match note_line_index {
827 0 => {
828 self.set_note_bullet()?;
829 write!(self, "{}", self.chars().note_bullet)?;
830 self.reset()?;
831 }
832 _ => write!(self, " ")?,
833 }
834 writeln!(self, " {line}",)?;
836 }
837
838 Ok(())
839 }
840
841 fn char_metrics(
845 &self,
846 char_indices: impl Iterator<Item = (usize, char)>,
847 ) -> impl Iterator<Item = (Metrics, char)> {
848 use unicode_width::UnicodeWidthChar;
849
850 let tab_width = self.config.tab_width;
851 let mut unicode_column = 0;
852
853 char_indices.map(move |(byte_index, ch)| {
854 let metrics = Metrics {
855 byte_index,
856 unicode_width: match (ch, tab_width) {
857 ('\t', 0) => 0, ('\t', _) => tab_width - (unicode_column % tab_width),
859 (ch, _) => ch.width().unwrap_or(0),
860 },
861 };
862 unicode_column += metrics.unicode_width;
863
864 (metrics, ch)
865 })
866 }
867
868 fn snippet_locus(&mut self, locus: &Locus) -> Result<(), Error> {
870 write!(
871 self,
872 "{name}:{line_number}:{column_number}",
873 name = locus.name,
874 line_number = locus.location.line_number,
875 column_number = locus.location.column_number,
876 )?;
877 Ok(())
878 }
879
880 fn outer_gutter(&mut self, outer_padding: usize) -> Result<(), Error> {
882 write!(self, "{space: >width$} ", space = "", width = outer_padding)?;
883 Ok(())
884 }
885
886 fn outer_gutter_number(
888 &mut self,
889 line_number: usize,
890 outer_padding: usize,
891 ) -> Result<(), Error> {
892 self.set_line_number()?;
893 write!(self, "{line_number: >outer_padding$}",)?;
894 self.reset()?;
895 write!(self, " ")?;
896 Ok(())
897 }
898
899 fn border_left(&mut self) -> Result<(), Error> {
901 self.set_source_border()?;
902 write!(self, "{}", self.chars().source_border_left)?;
903 self.reset()?;
904 Ok(())
905 }
906
907 fn border_left_break(&mut self) -> Result<(), Error> {
909 self.set_source_border()?;
910 write!(self, "{}", self.chars().source_border_left_break)?;
911 self.reset()?;
912 Ok(())
913 }
914
915 fn caret_pointers(
917 &mut self,
918 severity: Severity,
919 max_label_start: usize,
920 single_labels: &[SingleLabel<'_>],
921 trailing_label: Option<(usize, &SingleLabel<'_>)>,
922 char_indices: impl Iterator<Item = (usize, char)>,
923 ) -> Result<(), Error> {
924 for (metrics, ch) in self.char_metrics(char_indices) {
925 let column_range = metrics.byte_index..(metrics.byte_index + ch.len_utf8());
926 let label_style = hanging_labels(single_labels, trailing_label)
927 .filter(|(_, range, _)| column_range.contains(&range.start))
928 .map(|(label_style, _, _)| *label_style)
929 .max_by_key(label_priority_key);
930
931 let mut spaces = match label_style {
932 None => 0..metrics.unicode_width,
933 Some(label_style) => {
934 self.set_label(severity, label_style)?;
935 write!(self, "{}", self.chars().pointer_left)?;
936 self.reset()?;
937 1..metrics.unicode_width
938 }
939 };
940 if metrics.byte_index <= max_label_start {
942 spaces.try_for_each(|_| write!(self, " "))?;
943 }
944 }
945
946 Ok(())
947 }
948
949 fn label_multi_left(
955 &mut self,
956 severity: Severity,
957 label_style: LabelStyle,
958 underline: Option<LabelStyle>,
959 ) -> Result<(), Error> {
960 match underline {
961 None => write!(self, " ")?,
962 Some(label_style) => {
964 self.set_label(severity, label_style)?;
965 write!(self, "{}", self.chars().multi_top)?;
966 self.reset()?;
967 }
968 }
969 self.set_label(severity, label_style)?;
970 write!(self, "{}", self.chars().multi_left)?;
971 self.reset()?;
972 Ok(())
973 }
974
975 fn label_multi_top_left(
981 &mut self,
982 severity: Severity,
983 label_style: LabelStyle,
984 ) -> Result<(), Error> {
985 write!(self, " ")?;
986 self.set_label(severity, label_style)?;
987 write!(self, "{}", self.chars().multi_top_left)?;
988 self.reset()?;
989 Ok(())
990 }
991
992 fn label_multi_bottom_left(
998 &mut self,
999 severity: Severity,
1000 label_style: LabelStyle,
1001 ) -> Result<(), Error> {
1002 write!(self, " ")?;
1003 self.set_label(severity, label_style)?;
1004 write!(self, "{}", self.chars().multi_bottom_left)?;
1005 self.reset()?;
1006 Ok(())
1007 }
1008
1009 fn label_multi_top_caret(
1015 &mut self,
1016 severity: Severity,
1017 label_style: LabelStyle,
1018 source: &str,
1019 start: usize,
1020 ) -> Result<(), Error> {
1021 self.set_label(severity, label_style)?;
1022
1023 for (metrics, _) in self
1024 .char_metrics(source.char_indices())
1025 .take_while(|(metrics, _)| metrics.byte_index < start + 1)
1026 {
1027 (0..metrics.unicode_width)
1029 .try_for_each(|_| write!(self, "{}", self.chars().multi_top))?;
1030 }
1031
1032 let caret_start = match label_style {
1033 LabelStyle::Primary => self.config.chars.multi_primary_caret_start,
1034 LabelStyle::Secondary => self.config.chars.multi_secondary_caret_start,
1035 };
1036 write!(self, "{caret_start}",)?;
1037 self.reset()?;
1038 writeln!(self)?;
1039 Ok(())
1040 }
1041
1042 fn label_multi_bottom_caret(
1048 &mut self,
1049 severity: Severity,
1050 label_style: LabelStyle,
1051 source: &str,
1052 start: usize,
1053 message: &str,
1054 ) -> Result<(), Error> {
1055 self.set_label(severity, label_style)?;
1056
1057 for (metrics, _) in self
1058 .char_metrics(source.char_indices())
1059 .take_while(|(metrics, _)| metrics.byte_index < start)
1060 {
1061 (0..metrics.unicode_width)
1063 .try_for_each(|_| write!(self, "{}", self.chars().multi_bottom))?;
1064 }
1065
1066 let caret_end = match label_style {
1067 LabelStyle::Primary => self.config.chars.multi_primary_caret_start,
1068 LabelStyle::Secondary => self.config.chars.multi_secondary_caret_start,
1069 };
1070 write!(self, "{caret_end}")?;
1071 if !message.is_empty() {
1072 write!(self, " {message}")?;
1073 }
1074 self.reset()?;
1075 writeln!(self)?;
1076 Ok(())
1077 }
1078
1079 fn inner_gutter_column(
1081 &mut self,
1082 severity: Severity,
1083 underline: Option<Underline>,
1084 ) -> Result<(), Error> {
1085 match underline {
1086 None => self.inner_gutter_space(),
1087 Some((label_style, vertical_bound)) => {
1088 self.set_label(severity, label_style)?;
1089 let ch = match vertical_bound {
1090 VerticalBound::Top => self.config.chars.multi_top,
1091 VerticalBound::Bottom => self.config.chars.multi_bottom,
1092 };
1093 write!(self, "{ch}{ch}")?;
1094 self.reset()?;
1095 Ok(())
1096 }
1097 }
1098 }
1099
1100 fn inner_gutter_space(&mut self) -> Result<(), Error> {
1102 write!(self, " ")?;
1103 Ok(())
1104 }
1105
1106 fn inner_gutter(
1108 &mut self,
1109 severity: Severity,
1110 num_multi_labels: usize,
1111 multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)],
1112 ) -> Result<(), Error> {
1113 let mut multi_labels_iter = multi_labels.iter().peekable();
1114 for label_column in 0..num_multi_labels {
1115 match multi_labels_iter.peek() {
1116 Some((label_index, ls, label)) if *label_index == label_column => match label {
1117 MultiLabel::Left | MultiLabel::Bottom(..) => {
1118 self.label_multi_left(severity, *ls, None)?;
1119 multi_labels_iter.next();
1120 }
1121 MultiLabel::Top(..) => {
1122 self.inner_gutter_space()?;
1123 multi_labels_iter.next();
1124 }
1125 },
1126 Some((_, _, _)) | None => self.inner_gutter_space()?,
1127 }
1128 }
1129
1130 Ok(())
1131 }
1132}
1133
1134#[cfg(feature = "std")]
1135impl std::io::Write for Renderer<'_, '_> {
1136 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1137 self.writer.write(buf)
1138 }
1139
1140 fn flush(&mut self) -> std::io::Result<()> {
1141 self.writer.flush()
1142 }
1143}
1144
1145#[cfg(not(feature = "std"))]
1146impl core::fmt::Write for Renderer<'_, '_> {
1147 fn write_str(&mut self, s: &str) -> core::fmt::Result {
1148 self.writer.write_str(s)
1149 }
1150
1151 fn write_char(&mut self, c: char) -> core::fmt::Result {
1152 self.writer.write_char(c)
1153 }
1154
1155 fn write_fmt(&mut self, args: core::fmt::Arguments<'_>) -> core::fmt::Result {
1156 self.writer.write_fmt(args)
1157 }
1158}
1159
1160impl WriteStyle for Renderer<'_, '_> {
1161 fn set_header(&mut self, severity: Severity) -> GeneralWriteResult {
1162 self.writer.set_header(severity)
1163 }
1164
1165 fn set_header_message(&mut self) -> GeneralWriteResult {
1166 self.writer.set_header_message()
1167 }
1168
1169 fn set_line_number(&mut self) -> GeneralWriteResult {
1170 self.writer.set_line_number()
1171 }
1172
1173 fn set_note_bullet(&mut self) -> GeneralWriteResult {
1174 self.writer.set_note_bullet()
1175 }
1176
1177 fn set_source_border(&mut self) -> GeneralWriteResult {
1178 self.writer.set_source_border()
1179 }
1180
1181 fn set_label(&mut self, severity: Severity, label_style: LabelStyle) -> GeneralWriteResult {
1182 self.writer.set_label(severity, label_style)
1183 }
1184 fn reset(&mut self) -> GeneralWriteResult {
1185 self.writer.reset()
1186 }
1187}
1188
1189struct Metrics {
1190 byte_index: usize,
1191 unicode_width: usize,
1192}
1193
1194fn is_overlapping(range0: &Range<usize>, range1: &Range<usize>) -> bool {
1196 let start = core::cmp::max(range0.start, range1.start);
1197 let end = core::cmp::min(range0.end, range1.end);
1198 start < end
1199}
1200
1201#[allow(clippy::trivially_copy_pass_by_ref)]
1203fn label_priority_key(label_style: &LabelStyle) -> u8 {
1204 match label_style {
1205 LabelStyle::Secondary => 0,
1206 LabelStyle::Primary => 1,
1207 }
1208}
1209
1210fn hanging_labels<'labels, 'diagnostic>(
1213 single_labels: &'labels [SingleLabel<'diagnostic>],
1214 trailing_label: Option<(usize, &'labels SingleLabel<'diagnostic>)>,
1215) -> impl 'labels + DoubleEndedIterator<Item = &'labels SingleLabel<'diagnostic>> {
1216 single_labels
1217 .iter()
1218 .enumerate()
1219 .filter(|(_, (_, _, message))| !message.is_empty())
1220 .filter(move |(i, _)| trailing_label.map_or(true, |(j, _)| *i != j))
1221 .map(|(_, label)| label)
1222}