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 = "termcolor")]
9use {
10 crate::term::Styles,
11 termcolor::{ColorSpec, WriteColor},
12};
13
14#[cfg(feature = "std")]
15use std::io::{self, Write};
16
17#[cfg(not(feature = "std"))]
18use core::fmt::{Arguments, Write};
19
20pub struct Locus {
22 pub name: String,
24 pub location: Location,
26}
27
28pub type SingleLabel<'diagnostic> = (LabelStyle, Range<usize>, &'diagnostic str);
34
35pub enum MultiLabel<'diagnostic> {
39 Top(usize),
53 Left,
59 Bottom(usize, &'diagnostic str),
66}
67
68#[derive(Copy, Clone)]
69enum VerticalBound {
70 Top,
71 Bottom,
72}
73
74type Underline = (LabelStyle, VerticalBound);
75
76pub struct Renderer<'writer, 'config> {
123 #[cfg(feature = "termcolor")]
124 writer: &'writer mut dyn WriteColor,
125 #[cfg(not(feature = "termcolor"))]
126 writer: &'writer mut dyn Write,
127 config: &'config Config,
128}
129
130impl<'writer, 'config> Renderer<'writer, 'config> {
131 pub fn new(
133 #[cfg(feature = "termcolor")] writer: &'writer mut dyn WriteColor,
134 #[cfg(not(feature = "termcolor"))] writer: &'writer mut dyn Write,
135 config: &'config Config,
136 ) -> Renderer<'writer, 'config> {
137 Renderer { writer, config }
138 }
139
140 fn chars(&self) -> &'config Chars {
141 &self.config.chars
142 }
143
144 #[cfg(feature = "termcolor")]
145 fn styles(&self) -> &'config Styles {
146 &self.config.styles
147 }
148
149 pub fn render_header(
155 &mut self,
156 locus: Option<&Locus>,
157 severity: Severity,
158 code: Option<&str>,
159 message: &str,
160 ) -> Result<(), Error> {
161 if let Some(locus) = locus {
167 self.snippet_locus(locus)?;
168 write!(self, ": ")?;
169 }
170
171 #[cfg(feature = "termcolor")]
177 self.set_color(self.styles().header(severity))?;
178 match severity {
179 Severity::Bug => write!(self, "bug")?,
180 Severity::Error => write!(self, "error")?,
181 Severity::Warning => write!(self, "warning")?,
182 Severity::Help => write!(self, "help")?,
183 Severity::Note => write!(self, "note")?,
184 }
185
186 if let Some(code) = &code.filter(|code| !code.is_empty()) {
192 write!(self, "[{}]", code)?;
193 }
194
195 #[cfg(feature = "termcolor")]
201 self.set_color(&self.styles().header_message)?;
202 write!(self, ": {}", message)?;
203 #[cfg(feature = "termcolor")]
204 self.reset()?;
205
206 writeln!(self)?;
207
208 Ok(())
209 }
210
211 pub fn render_empty(&mut self) -> Result<(), Error> {
213 writeln!(self)?;
214 Ok(())
215 }
216
217 pub fn render_snippet_start(
223 &mut self,
224 outer_padding: usize,
225 locus: &Locus,
226 ) -> Result<(), Error> {
227 self.outer_gutter(outer_padding)?;
228
229 #[cfg(feature = "termcolor")]
230 self.set_color(&self.styles().source_border)?;
231 write!(self, "{}", self.chars().snippet_start)?;
232 #[cfg(feature = "termcolor")]
233 self.reset()?;
234
235 write!(self, " ")?;
236 self.snippet_locus(locus)?;
237
238 writeln!(self)?;
239
240 Ok(())
241 }
242
243 #[allow(clippy::too_many_arguments)]
250 pub fn render_snippet_source(
251 &mut self,
252 outer_padding: usize,
253 line_number: usize,
254 source: &str,
255 severity: Severity,
256 single_labels: &[SingleLabel<'_>],
257 num_multi_labels: usize,
258 multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)],
259 ) -> Result<(), Error> {
260 let source = source.trim_end_matches(['\n', '\r', '\0'].as_ref());
263
264 {
270 self.outer_gutter_number(line_number, outer_padding)?;
272 self.border_left()?;
273
274 let mut multi_labels_iter = multi_labels.iter().peekable();
276 for label_column in 0..num_multi_labels {
277 match multi_labels_iter.peek() {
278 Some((label_index, label_style, label)) if *label_index == label_column => {
279 match label {
280 MultiLabel::Top(start)
281 if *start <= source.len() - source.trim_start().len() =>
282 {
283 self.label_multi_top_left(severity, *label_style)?;
284 }
285 MultiLabel::Top(..) => self.inner_gutter_space()?,
286 MultiLabel::Left | MultiLabel::Bottom(..) => {
287 self.label_multi_left(severity, *label_style, None)?;
288 }
289 }
290 multi_labels_iter.next();
291 }
292 Some((_, _, _)) | None => self.inner_gutter_space()?,
293 }
294 }
295
296 write!(self, " ")?;
298 let mut in_primary = false;
299 for (metrics, ch) in self.char_metrics(source.char_indices()) {
300 let column_range = metrics.byte_index..(metrics.byte_index + ch.len_utf8());
301
302 let is_primary = single_labels.iter().any(|(ls, range, _)| {
304 *ls == LabelStyle::Primary && is_overlapping(range, &column_range)
305 }) || multi_labels.iter().any(|(_, ls, label)| {
306 *ls == LabelStyle::Primary
307 && match label {
308 MultiLabel::Top(start) => column_range.start >= *start,
309 MultiLabel::Left => true,
310 MultiLabel::Bottom(start, _) => column_range.end <= *start,
311 }
312 });
313
314 if is_primary && !in_primary {
316 #[cfg(feature = "termcolor")]
317 self.set_color(self.styles().label(severity, LabelStyle::Primary))?;
318 in_primary = true;
319 } else if !is_primary && in_primary {
320 #[cfg(feature = "termcolor")]
321 self.reset()?;
322 in_primary = false;
323 }
324
325 match ch {
326 '\t' => (0..metrics.unicode_width).try_for_each(|_| write!(self, " "))?,
327 _ => write!(self, "{}", ch)?,
328 }
329 }
330 if in_primary {
331 #[cfg(feature = "termcolor")]
332 self.reset()?;
333 }
334 writeln!(self)?;
335 }
336
337 if !single_labels.is_empty() {
347 let mut num_messages = 0;
368 let mut max_label_start = 0;
376 let mut max_label_end = 0;
384 let mut trailing_label = None;
390
391 for (label_index, label) in single_labels.iter().enumerate() {
392 let (_, range, message) = label;
393 if !message.is_empty() {
394 num_messages += 1;
395 }
396 max_label_start = core::cmp::max(max_label_start, range.start);
397 max_label_end = core::cmp::max(max_label_end, range.end);
398 if range.end == max_label_end {
400 if message.is_empty() {
401 trailing_label = None;
402 } else {
403 trailing_label = Some((label_index, label));
404 }
405 }
406 }
407 if let Some((trailing_label_index, (_, trailing_range, _))) = trailing_label {
408 if single_labels
411 .iter()
412 .enumerate()
413 .filter(|(label_index, _)| *label_index != trailing_label_index)
414 .any(|(_, (_, range, _))| is_overlapping(trailing_range, range))
415 {
416 trailing_label = None;
419 }
420 }
421
422 self.outer_gutter(outer_padding)?;
428 self.border_left()?;
429 self.inner_gutter(severity, num_multi_labels, multi_labels)?;
430 write!(self, " ")?;
431
432 let mut previous_label_style = None;
433 let placeholder_metrics = Metrics {
434 byte_index: source.len(),
435 unicode_width: 1,
436 };
437 for (metrics, ch) in self
438 .char_metrics(source.char_indices())
439 .chain(core::iter::once((placeholder_metrics, '\0')))
447 {
448 let column_range = metrics.byte_index..(metrics.byte_index + ch.len_utf8());
450 let current_label_style = single_labels
451 .iter()
452 .filter(|(_, range, _)| is_overlapping(range, &column_range))
453 .map(|(label_style, _, _)| *label_style)
454 .max_by_key(label_priority_key);
455
456 if previous_label_style != current_label_style {
458 match current_label_style {
459 None => {
460 #[cfg(feature = "termcolor")]
461 self.reset()?;
462 }
463 #[cfg_attr(not(feature = "termcolor"), allow(unused))]
464 Some(label_style) => {
465 #[cfg(feature = "termcolor")]
466 self.set_color(self.styles().label(severity, label_style))?;
467 }
468 }
469 }
470
471 let caret_ch = match current_label_style {
472 Some(LabelStyle::Primary) => Some(self.chars().single_primary_caret),
473 Some(LabelStyle::Secondary) => Some(self.chars().single_secondary_caret),
474 None if metrics.byte_index < max_label_end => Some(' '),
476 None => None,
477 };
478 if let Some(caret_ch) = caret_ch {
479 (0..metrics.unicode_width).try_for_each(|_| write!(self, "{}", caret_ch))?;
481 }
482
483 previous_label_style = current_label_style;
484 }
485 if previous_label_style.is_some() {
487 #[cfg(feature = "termcolor")]
488 self.reset()?;
489 }
490 #[cfg_attr(not(feature = "termcolor"), allow(unused))]
492 if let Some((_, (label_style, _, message))) = trailing_label {
493 write!(self, " ")?;
494 #[cfg(feature = "termcolor")]
495 self.set_color(self.styles().label(severity, *label_style))?;
496 write!(self, "{}", message)?;
497 #[cfg(feature = "termcolor")]
498 self.reset()?;
499 }
500 writeln!(self)?;
501
502 if num_messages > trailing_label.iter().count() {
511 self.outer_gutter(outer_padding)?;
517 self.border_left()?;
518 self.inner_gutter(severity, num_multi_labels, multi_labels)?;
519 write!(self, " ")?;
520 self.caret_pointers(
521 severity,
522 max_label_start,
523 single_labels,
524 trailing_label,
525 source.char_indices(),
526 )?;
527 writeln!(self)?;
528
529 #[cfg_attr(not(feature = "termcolor"), allow(unused))]
537 for (label_style, range, message) in
538 hanging_labels(single_labels, trailing_label).rev()
539 {
540 self.outer_gutter(outer_padding)?;
541 self.border_left()?;
542 self.inner_gutter(severity, num_multi_labels, multi_labels)?;
543 write!(self, " ")?;
544 self.caret_pointers(
545 severity,
546 max_label_start,
547 single_labels,
548 trailing_label,
549 source
550 .char_indices()
551 .take_while(|(byte_index, _)| *byte_index < range.start),
552 )?;
553 #[cfg(feature = "termcolor")]
554 self.set_color(self.styles().label(severity, *label_style))?;
555 write!(self, "{}", message)?;
556 #[cfg(feature = "termcolor")]
557 self.reset()?;
558 writeln!(self)?;
559 }
560 }
561 }
562
563 for (multi_label_index, (_, label_style, label)) in multi_labels.iter().enumerate() {
570 let (label_style, range, bottom_message) = match label {
571 MultiLabel::Left => continue, MultiLabel::Top(start) if *start <= source.len() - source.trim_start().len() => {
574 continue
575 }
576 MultiLabel::Top(range) => (*label_style, range, None),
577 MultiLabel::Bottom(range, message) => (*label_style, range, Some(message)),
578 };
579
580 self.outer_gutter(outer_padding)?;
581 self.border_left()?;
582
583 let mut underline = None;
589 let mut multi_labels_iter = multi_labels.iter().enumerate().peekable();
590 for label_column in 0..num_multi_labels {
591 match multi_labels_iter.peek() {
592 Some((i, (label_index, ls, label))) if *label_index == label_column => {
593 match label {
594 MultiLabel::Left => {
595 self.label_multi_left(severity, *ls, underline.map(|(s, _)| s))?;
596 }
597 MultiLabel::Top(..) if multi_label_index > *i => {
598 self.label_multi_left(severity, *ls, underline.map(|(s, _)| s))?;
599 }
600 MultiLabel::Bottom(..) if multi_label_index < *i => {
601 self.label_multi_left(severity, *ls, underline.map(|(s, _)| s))?;
602 }
603 MultiLabel::Top(..) if multi_label_index == *i => {
604 underline = Some((*ls, VerticalBound::Top));
605 self.label_multi_top_left(severity, label_style)?
606 }
607 MultiLabel::Bottom(..) if multi_label_index == *i => {
608 underline = Some((*ls, VerticalBound::Bottom));
609 self.label_multi_bottom_left(severity, label_style)?;
610 }
611 MultiLabel::Top(..) | MultiLabel::Bottom(..) => {
612 self.inner_gutter_column(severity, underline)?;
613 }
614 }
615 multi_labels_iter.next();
616 }
617 Some((_, _)) | None => self.inner_gutter_column(severity, underline)?,
618 }
619 }
620
621 match bottom_message {
623 None => self.label_multi_top_caret(severity, label_style, source, *range)?,
624 Some(message) => {
625 self.label_multi_bottom_caret(severity, label_style, source, *range, message)?
626 }
627 }
628 }
629
630 Ok(())
631 }
632
633 pub fn render_snippet_empty(
639 &mut self,
640 outer_padding: usize,
641 severity: Severity,
642 num_multi_labels: usize,
643 multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)],
644 ) -> Result<(), Error> {
645 self.outer_gutter(outer_padding)?;
646 self.border_left()?;
647 self.inner_gutter(severity, num_multi_labels, multi_labels)?;
648 writeln!(self)?;
649 Ok(())
650 }
651
652 pub fn render_snippet_break(
658 &mut self,
659 outer_padding: usize,
660 severity: Severity,
661 num_multi_labels: usize,
662 multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)],
663 ) -> Result<(), Error> {
664 self.outer_gutter(outer_padding)?;
665 self.border_left_break()?;
666 self.inner_gutter(severity, num_multi_labels, multi_labels)?;
667 writeln!(self)?;
668 Ok(())
669 }
670
671 pub fn render_snippet_note(
678 &mut self,
679 outer_padding: usize,
680 message: &str,
681 ) -> Result<(), Error> {
682 for (note_line_index, line) in message.lines().enumerate() {
683 self.outer_gutter(outer_padding)?;
684 match note_line_index {
685 0 => {
686 #[cfg(feature = "termcolor")]
687 self.set_color(&self.styles().note_bullet)?;
688 write!(self, "{}", self.chars().note_bullet)?;
689 #[cfg(feature = "termcolor")]
690 self.reset()?;
691 }
692 _ => write!(self, " ")?,
693 }
694 writeln!(self, " {}", line)?;
696 }
697
698 Ok(())
699 }
700
701 fn char_metrics(
705 &self,
706 char_indices: impl Iterator<Item = (usize, char)>,
707 ) -> impl Iterator<Item = (Metrics, char)> {
708 use unicode_width::UnicodeWidthChar;
709
710 let tab_width = self.config.tab_width;
711 let mut unicode_column = 0;
712
713 char_indices.map(move |(byte_index, ch)| {
714 let metrics = Metrics {
715 byte_index,
716 unicode_width: match (ch, tab_width) {
717 ('\t', 0) => 0, ('\t', _) => tab_width - (unicode_column % tab_width),
719 (ch, _) => ch.width().unwrap_or(0),
720 },
721 };
722 unicode_column += metrics.unicode_width;
723
724 (metrics, ch)
725 })
726 }
727
728 fn snippet_locus(&mut self, locus: &Locus) -> Result<(), Error> {
730 write!(
731 self,
732 "{name}:{line_number}:{column_number}",
733 name = locus.name,
734 line_number = locus.location.line_number,
735 column_number = locus.location.column_number,
736 )?;
737 Ok(())
738 }
739
740 fn outer_gutter(&mut self, outer_padding: usize) -> Result<(), Error> {
742 write!(self, "{space: >width$} ", space = "", width = outer_padding)?;
743 Ok(())
744 }
745
746 fn outer_gutter_number(
748 &mut self,
749 line_number: usize,
750 outer_padding: usize,
751 ) -> Result<(), Error> {
752 #[cfg(feature = "termcolor")]
753 self.set_color(&self.styles().line_number)?;
754 write!(
755 self,
756 "{line_number: >width$}",
757 line_number = line_number,
758 width = outer_padding,
759 )?;
760 #[cfg(feature = "termcolor")]
761 self.reset()?;
762 write!(self, " ")?;
763 Ok(())
764 }
765
766 fn border_left(&mut self) -> Result<(), Error> {
768 #[cfg(feature = "termcolor")]
769 self.set_color(&self.styles().source_border)?;
770 write!(self, "{}", self.chars().source_border_left)?;
771 #[cfg(feature = "termcolor")]
772 self.reset()?;
773 Ok(())
774 }
775
776 fn border_left_break(&mut self) -> Result<(), Error> {
778 #[cfg(feature = "termcolor")]
779 self.set_color(&self.styles().source_border)?;
780 write!(self, "{}", self.chars().source_border_left_break)?;
781 #[cfg(feature = "termcolor")]
782 self.reset()?;
783 Ok(())
784 }
785
786 fn caret_pointers(
788 &mut self,
789 #[cfg_attr(not(feature = "termcolor"), allow(unused))] severity: Severity,
790 max_label_start: usize,
791 single_labels: &[SingleLabel<'_>],
792 trailing_label: Option<(usize, &SingleLabel<'_>)>,
793 char_indices: impl Iterator<Item = (usize, char)>,
794 ) -> Result<(), Error> {
795 for (metrics, ch) in self.char_metrics(char_indices) {
796 let column_range = metrics.byte_index..(metrics.byte_index + ch.len_utf8());
797 let label_style = hanging_labels(single_labels, trailing_label)
798 .filter(|(_, range, _)| column_range.contains(&range.start))
799 .map(|(label_style, _, _)| *label_style)
800 .max_by_key(label_priority_key);
801
802 let mut spaces = match label_style {
803 None => 0..metrics.unicode_width,
804 #[cfg_attr(not(feature = "termcolor"), allow(unused))]
805 Some(label_style) => {
806 #[cfg(feature = "termcolor")]
807 self.set_color(self.styles().label(severity, label_style))?;
808 write!(self, "{}", self.chars().pointer_left)?;
809 #[cfg(feature = "termcolor")]
810 self.reset()?;
811 1..metrics.unicode_width
812 }
813 };
814 if metrics.byte_index <= max_label_start {
816 spaces.try_for_each(|_| write!(self, " "))?;
817 }
818 }
819
820 Ok(())
821 }
822
823 fn label_multi_left(
829 &mut self,
830 #[cfg_attr(not(feature = "termcolor"), allow(unused))] severity: Severity,
831 #[cfg_attr(not(feature = "termcolor"), allow(unused))] label_style: LabelStyle,
832 underline: Option<LabelStyle>,
833 ) -> Result<(), Error> {
834 match underline {
835 None => write!(self, " ")?,
836 #[cfg_attr(not(feature = "termcolor"), allow(unused))]
838 Some(label_style) => {
839 #[cfg(feature = "termcolor")]
840 self.set_color(self.styles().label(severity, label_style))?;
841 write!(self, "{}", self.chars().multi_top)?;
842 #[cfg(feature = "termcolor")]
843 self.reset()?;
844 }
845 }
846 #[cfg(feature = "termcolor")]
847 self.set_color(self.styles().label(severity, label_style))?;
848 write!(self, "{}", self.chars().multi_left)?;
849 #[cfg(feature = "termcolor")]
850 self.reset()?;
851 Ok(())
852 }
853
854 fn label_multi_top_left(
860 &mut self,
861 #[cfg_attr(not(feature = "termcolor"), allow(unused))] severity: Severity,
862 #[cfg_attr(not(feature = "termcolor"), allow(unused))] label_style: LabelStyle,
863 ) -> Result<(), Error> {
864 write!(self, " ")?;
865 #[cfg(feature = "termcolor")]
866 self.set_color(self.styles().label(severity, label_style))?;
867 write!(self, "{}", self.chars().multi_top_left)?;
868 #[cfg(feature = "termcolor")]
869 self.reset()?;
870 Ok(())
871 }
872
873 fn label_multi_bottom_left(
879 &mut self,
880 #[cfg_attr(not(feature = "termcolor"), allow(unused))] severity: Severity,
881 #[cfg_attr(not(feature = "termcolor"), allow(unused))] label_style: LabelStyle,
882 ) -> Result<(), Error> {
883 write!(self, " ")?;
884 #[cfg(feature = "termcolor")]
885 self.set_color(self.styles().label(severity, label_style))?;
886 write!(self, "{}", self.chars().multi_bottom_left)?;
887 #[cfg(feature = "termcolor")]
888 self.reset()?;
889 Ok(())
890 }
891
892 fn label_multi_top_caret(
898 &mut self,
899 #[cfg_attr(not(feature = "termcolor"), allow(unused))] severity: Severity,
900 label_style: LabelStyle,
901 source: &str,
902 start: usize,
903 ) -> Result<(), Error> {
904 #[cfg(feature = "termcolor")]
905 self.set_color(self.styles().label(severity, label_style))?;
906
907 for (metrics, _) in self
908 .char_metrics(source.char_indices())
909 .take_while(|(metrics, _)| metrics.byte_index < start + 1)
910 {
911 (0..metrics.unicode_width)
913 .try_for_each(|_| write!(self, "{}", self.chars().multi_top))?;
914 }
915
916 let caret_start = match label_style {
917 LabelStyle::Primary => self.config.chars.multi_primary_caret_start,
918 LabelStyle::Secondary => self.config.chars.multi_secondary_caret_start,
919 };
920 write!(self, "{}", caret_start)?;
921 #[cfg(feature = "termcolor")]
922 self.reset()?;
923 writeln!(self)?;
924 Ok(())
925 }
926
927 fn label_multi_bottom_caret(
933 &mut self,
934 #[cfg_attr(not(feature = "termcolor"), allow(unused))] severity: Severity,
935 label_style: LabelStyle,
936 source: &str,
937 start: usize,
938 message: &str,
939 ) -> Result<(), Error> {
940 #[cfg(feature = "termcolor")]
941 self.set_color(self.styles().label(severity, label_style))?;
942
943 for (metrics, _) in self
944 .char_metrics(source.char_indices())
945 .take_while(|(metrics, _)| metrics.byte_index < start)
946 {
947 (0..metrics.unicode_width)
949 .try_for_each(|_| write!(self, "{}", self.chars().multi_bottom))?;
950 }
951
952 let caret_end = match label_style {
953 LabelStyle::Primary => self.config.chars.multi_primary_caret_start,
954 LabelStyle::Secondary => self.config.chars.multi_secondary_caret_start,
955 };
956 write!(self, "{}", caret_end)?;
957 if !message.is_empty() {
958 write!(self, " {}", message)?;
959 }
960 #[cfg(feature = "termcolor")]
961 self.reset()?;
962 writeln!(self)?;
963 Ok(())
964 }
965
966 fn inner_gutter_column(
968 &mut self,
969 #[cfg_attr(not(feature = "termcolor"), allow(unused))] severity: Severity,
970 underline: Option<Underline>,
971 ) -> Result<(), Error> {
972 match underline {
973 None => self.inner_gutter_space(),
974 #[cfg_attr(not(feature = "termcolor"), allow(unused))]
975 Some((label_style, vertical_bound)) => {
976 #[cfg(feature = "termcolor")]
977 self.set_color(self.styles().label(severity, label_style))?;
978 let ch = match vertical_bound {
979 VerticalBound::Top => self.config.chars.multi_top,
980 VerticalBound::Bottom => self.config.chars.multi_bottom,
981 };
982 write!(self, "{0}{0}", ch)?;
983 #[cfg(feature = "termcolor")]
984 self.reset()?;
985 Ok(())
986 }
987 }
988 }
989
990 fn inner_gutter_space(&mut self) -> Result<(), Error> {
992 write!(self, " ")?;
993 Ok(())
994 }
995
996 fn inner_gutter(
998 &mut self,
999 severity: Severity,
1000 num_multi_labels: usize,
1001 multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)],
1002 ) -> Result<(), Error> {
1003 let mut multi_labels_iter = multi_labels.iter().peekable();
1004 for label_column in 0..num_multi_labels {
1005 match multi_labels_iter.peek() {
1006 Some((label_index, ls, label)) if *label_index == label_column => match label {
1007 MultiLabel::Left | MultiLabel::Bottom(..) => {
1008 self.label_multi_left(severity, *ls, None)?;
1009 multi_labels_iter.next();
1010 }
1011 MultiLabel::Top(..) => {
1012 self.inner_gutter_space()?;
1013 multi_labels_iter.next();
1014 }
1015 },
1016 Some((_, _, _)) | None => self.inner_gutter_space()?,
1017 }
1018 }
1019
1020 Ok(())
1021 }
1022}
1023
1024#[cfg(not(feature = "std"))]
1025impl Write for Renderer<'_, '_> {
1026 fn write_str(&mut self, s: &str) -> core::fmt::Result {
1027 self.writer.write_str(s)
1028 }
1029
1030 fn write_char(&mut self, c: char) -> core::fmt::Result {
1031 self.writer.write_char(c)
1032 }
1033
1034 fn write_fmt(&mut self, args: Arguments<'_>) -> core::fmt::Result {
1035 self.writer.write_fmt(args)
1036 }
1037}
1038
1039#[cfg(feature = "std")]
1040impl Write for Renderer<'_, '_> {
1041 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
1042 self.writer.write(buf)
1043 }
1044
1045 fn flush(&mut self) -> io::Result<()> {
1046 self.writer.flush()
1047 }
1048}
1049
1050#[cfg(feature = "termcolor")]
1051impl WriteColor for Renderer<'_, '_> {
1052 fn supports_color(&self) -> bool {
1053 self.writer.supports_color()
1054 }
1055
1056 fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
1057 self.writer.set_color(spec)
1058 }
1059
1060 fn reset(&mut self) -> io::Result<()> {
1061 self.writer.reset()
1062 }
1063
1064 fn is_synchronous(&self) -> bool {
1065 self.writer.is_synchronous()
1066 }
1067}
1068
1069struct Metrics {
1070 byte_index: usize,
1071 unicode_width: usize,
1072}
1073
1074fn is_overlapping(range0: &Range<usize>, range1: &Range<usize>) -> bool {
1076 let start = core::cmp::max(range0.start, range1.start);
1077 let end = core::cmp::min(range0.end, range1.end);
1078 start < end
1079}
1080
1081fn label_priority_key(label_style: &LabelStyle) -> u8 {
1083 match label_style {
1084 LabelStyle::Secondary => 0,
1085 LabelStyle::Primary => 1,
1086 }
1087}
1088
1089fn hanging_labels<'labels, 'diagnostic>(
1092 single_labels: &'labels [SingleLabel<'diagnostic>],
1093 trailing_label: Option<(usize, &'labels SingleLabel<'diagnostic>)>,
1094) -> impl 'labels + DoubleEndedIterator<Item = &'labels SingleLabel<'diagnostic>> {
1095 single_labels
1096 .iter()
1097 .enumerate()
1098 .filter(|(_, (_, _, message))| !message.is_empty())
1099 .filter(move |(i, _)| trailing_label.map_or(true, |(j, _)| *i != j))
1100 .map(|(_, label)| label)
1101}