1use std::{
36 borrow::Cow,
37 fmt::{Display, Formatter},
38 iter::repeat,
39 mem::{replace, take},
40};
41
42use crate::{
43 format::{terminal::Escaped, Style},
44 lexis::{
45 Column,
46 Length,
47 Line,
48 Position,
49 PositionSpan,
50 Site,
51 SiteSpan,
52 SourceCode,
53 ToSite,
54 ToSpan,
55 Token,
56 TokenBuffer,
57 },
58 report::ld_unreachable,
59};
60
61#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
66#[non_exhaustive]
67pub struct SnippetConfig {
68 pub show_numbers: bool,
70
71 pub draw_frame: bool,
73
74 pub dim_code: bool,
78
79 pub ascii_drawing: bool,
82
83 pub style: bool,
89
90 pub caption: bool,
92
93 pub summary: bool,
95}
96
97impl Default for SnippetConfig {
98 #[inline(always)]
99 fn default() -> Self {
100 Self::verbose()
101 }
102}
103
104impl SnippetConfig {
105 #[inline(always)]
107 pub const fn verbose() -> Self {
108 Self {
109 show_numbers: true,
110 draw_frame: true,
111 dim_code: true,
112 ascii_drawing: false,
113 style: true,
114 caption: true,
115 summary: true,
116 }
117 }
118
119 #[inline(always)]
124 pub const fn minimal() -> Self {
125 Self {
126 show_numbers: false,
127 draw_frame: false,
128 dim_code: false,
129 ascii_drawing: false,
130 style: false,
131 caption: false,
132 summary: false,
133 }
134 }
135
136 #[inline(always)]
137 fn cover(&self) -> usize {
138 2
139 }
140
141 #[inline(always)]
142 fn continuation(&self) -> usize {
143 3
144 }
145
146 #[inline(always)]
147 fn margin(&self) -> Length {
148 80
149 }
150
151 #[inline(always)]
152 fn code_style(&self, dim: bool) -> Style {
153 match self.style && self.dim_code && dim {
154 false => Style::default(),
155 true => Style::default().bright_black(),
156 }
157 }
158
159 #[inline(always)]
160 fn annotation_style(&self, priority: AnnotationPriority) -> Style {
161 if !self.style {
162 return Style::default();
163 }
164
165 match priority {
166 AnnotationPriority::Default => Style::default().invert(),
167 AnnotationPriority::Primary => Style::default().invert().red(),
168 AnnotationPriority::Secondary => Style::default().invert().blue(),
169 AnnotationPriority::Note => Style::default().invert().yellow(),
170 }
171 }
172
173 #[inline(always)]
174 fn control(&self) -> char {
175 match self.ascii_drawing {
176 true => ' ',
177 false => '💻',
178 }
179 }
180
181 #[inline(always)]
182 fn placeholder(&self) -> char {
183 match self.ascii_drawing {
184 true => ' ',
185 false => ' ',
186 }
187 }
188
189 #[inline(always)]
190 fn etc(&self) -> &'static PrintString<'static> {
191 static ASCII: PrintString<'static> = PrintString::borrowed("...");
192 static NON_ASCII: PrintString<'static> = PrintString::borrowed("…");
193
194 match self.ascii_drawing {
195 true => &ASCII,
196 false => &NON_ASCII,
197 }
198 }
199
200 #[inline(always)]
201 fn tab(&self) -> &'static PrintString<'static> {
202 static STRING: PrintString<'static> = PrintString::borrowed(" ");
203
204 &STRING
205 }
206
207 #[inline(always)]
208 fn box_vertical(&self) -> &'static PrintString<'static> {
209 static ASCII: PrintString<'static> = PrintString::borrowed("|");
210 static NON_ASCII: PrintString<'static> = PrintString::borrowed("│");
211
212 match self.ascii_drawing {
213 true => &ASCII,
214 false => &NON_ASCII,
215 }
216 }
217
218 #[inline(always)]
219 fn box_horizontal(&self) -> &'static PrintString<'static> {
220 static ASCII: PrintString<'static> = PrintString::borrowed("-");
221 static NON_ASCII: PrintString<'static> = PrintString::borrowed("─");
222
223 match self.ascii_drawing {
224 true => &ASCII,
225 false => &NON_ASCII,
226 }
227 }
228
229 #[inline(always)]
230 fn box_top_left(&self) -> &'static PrintString<'static> {
231 static ASCII: PrintString<'static> = PrintString::borrowed(" ");
232 static NON_ASCII: PrintString<'static> = PrintString::borrowed("╭");
233
234 match self.ascii_drawing {
235 true => &ASCII,
236 false => &NON_ASCII,
237 }
238 }
239
240 #[inline(always)]
241 fn box_top_right(&self) -> &'static PrintString<'static> {
242 static ASCII: PrintString<'static> = PrintString::borrowed("");
243 static NON_ASCII: PrintString<'static> = PrintString::borrowed("╮");
244
245 match self.ascii_drawing {
246 true => &ASCII,
247 false => &NON_ASCII,
248 }
249 }
250
251 #[inline(always)]
252 fn box_bottom_left(&self) -> &'static PrintString<'static> {
253 static ASCII: PrintString<'static> = PrintString::borrowed(" ");
254 static NON_ASCII: PrintString<'static> = PrintString::borrowed("╰");
255
256 match self.ascii_drawing {
257 true => &ASCII,
258 false => &NON_ASCII,
259 }
260 }
261
262 #[inline(always)]
263 fn box_bottom_right(&self) -> &'static PrintString<'static> {
264 static ASCII: PrintString<'static> = PrintString::borrowed("");
265 static NON_ASCII: PrintString<'static> = PrintString::borrowed("╯");
266
267 match self.ascii_drawing {
268 true => &ASCII,
269 false => &NON_ASCII,
270 }
271 }
272
273 #[inline(always)]
274 fn box_middle_delimiter(&self) -> &'static PrintString<'static> {
275 static ASCII: PrintString<'static> = PrintString::borrowed("|=");
276 static ASCII_ALONE: PrintString<'static> = PrintString::borrowed("|==");
277 static NON_ASCII: PrintString<'static> = PrintString::borrowed("╞═");
278 static NON_ASCII_ALONE: PrintString<'static> = PrintString::borrowed("╞══");
279
280 match (self.ascii_drawing, self.draw_frame) {
281 (true, true) => &ASCII,
282 (true, false) => &ASCII_ALONE,
283 (false, true) => &NON_ASCII,
284 (false, false) => &NON_ASCII_ALONE,
285 }
286 }
287
288 #[inline(always)]
289 fn box_middle_left(&self) -> &'static PrintString<'static> {
290 static ASCII: PrintString<'static> = PrintString::borrowed("|");
291 static NON_ASCII: PrintString<'static> = PrintString::borrowed("├");
292
293 match self.ascii_drawing {
294 true => &ASCII,
295 false => &NON_ASCII,
296 }
297 }
298
299 #[inline(always)]
300 fn box_middle_right(&self) -> &'static PrintString<'static> {
301 static ASCII: PrintString<'static> = PrintString::borrowed("|");
302 static NON_ASCII: PrintString<'static> = PrintString::borrowed("┤");
303
304 match self.ascii_drawing {
305 true => &ASCII,
306 false => &NON_ASCII,
307 }
308 }
309
310 #[inline(always)]
311 fn caption_start(&self) -> &'static PrintString<'static> {
312 static ASCII: PrintString<'static> = PrintString::borrowed("-[ ");
313 static NON_ASCII: PrintString<'static> = PrintString::borrowed("─╢ ");
314
315 match self.ascii_drawing {
316 true => &ASCII,
317 false => &NON_ASCII,
318 }
319 }
320
321 #[inline(always)]
322 fn caption_end(&self) -> &'static PrintString<'static> {
323 static ASCII: PrintString<'static> = PrintString::borrowed(" ]");
324 static NON_ASCII: PrintString<'static> = PrintString::borrowed(" ╟");
325 static NON_ASCII_ALONE: PrintString<'static> = PrintString::borrowed(" ║");
326
327 match self.ascii_drawing {
328 true => &ASCII,
329 false => match self.draw_frame {
330 true => &NON_ASCII,
331 false => &NON_ASCII_ALONE,
332 },
333 }
334 }
335
336 #[inline(always)]
337 fn arrow_up_right(&self) -> &'static PrintString<'static> {
338 static ASCII: PrintString<'static> = PrintString::borrowed("|- ");
339 static NON_ASCII: PrintString<'static> = PrintString::borrowed("╭╴ ");
340
341 match self.ascii_drawing {
342 true => &ASCII,
343 false => &NON_ASCII,
344 }
345 }
346
347 #[inline(always)]
348 fn arrow_down_right(&self) -> &'static PrintString<'static> {
349 static ASCII: PrintString<'static> = PrintString::borrowed("|- ");
350 static NON_ASCII: PrintString<'static> = PrintString::borrowed("╰╴ ");
351
352 match self.ascii_drawing {
353 true => &ASCII,
354 false => &NON_ASCII,
355 }
356 }
357
358 #[inline(always)]
359 fn arrow_down_middle(&self) -> &'static PrintString<'static> {
360 static ASCII: PrintString<'static> = PrintString::borrowed("|");
361 static NON_ASCII: PrintString<'static> = PrintString::borrowed("│");
362
363 match self.ascii_drawing {
364 true => &ASCII,
365 false => &NON_ASCII,
366 }
367 }
368}
369
370pub trait SnippetFormatter<'f> {
373 fn snippet<'a, C: SourceCode>(&'a mut self, code: &'a C) -> Snippet<'a, 'f, C>;
389}
390
391impl<'f> SnippetFormatter<'f> for Formatter<'f> {
392 #[inline(always)]
393 fn snippet<'a, C: SourceCode>(&'a mut self, code: &'a C) -> Snippet<'a, 'f, C> {
394 static VERBOSE: SnippetConfig = SnippetConfig::verbose();
395 static MINIMAL: SnippetConfig = SnippetConfig::minimal();
396
397 let config = match self.alternate() {
398 true => &VERBOSE,
399 false => &MINIMAL,
400 };
401
402 Snippet {
403 formatter: self,
404 code,
405 config,
406 caption: PrintString::empty(),
407 summary: PrintString::empty(),
408 highlighter: None,
409 annotations: Vec::with_capacity(4),
410 }
411 }
412}
413
414pub struct Snippet<'a, 'f, C: SourceCode> {
432 formatter: &'a mut Formatter<'f>,
433 code: &'a C,
434 config: &'a SnippetConfig,
435 caption: PrintString<'a>,
436 summary: PrintString<'a>,
437 highlighter: Option<Box<dyn Highlighter<C::Token> + 'a>>,
438 annotations: Vec<Annotation<'a>>,
439}
440
441impl<'a, 'f, C: SourceCode> Snippet<'a, 'f, C> {
442 #[inline(always)]
444 pub fn set_config(&mut self, config: &'a SnippetConfig) -> &mut Self {
445 self.config = config;
446
447 self
448 }
449
450 #[inline(always)]
456 pub fn set_caption(&mut self, caption: impl Into<Cow<'a, str>>) -> &mut Self {
457 let caption = caption.into();
458
459 if caption.contains('\n') {
460 panic!("Multiline captions not supported.");
461 }
462
463 self.caption = PrintString::from_cow(caption);
464
465 self
466 }
467
468 #[inline(always)]
470 pub fn set_summary(&mut self, summary: impl Into<Cow<'a, str>>) -> &mut Self {
471 self.summary = PrintString::from_cow(summary.into());
472
473 self
474 }
475
476 #[inline(always)]
484 pub fn set_highlighter(&mut self, highlighter: impl Highlighter<C::Token> + 'a) -> &mut Self {
485 self.highlighter = Some(Box::new(highlighter));
486
487 self
488 }
489
490 pub fn annotate(
513 &mut self,
514 span: impl ToSpan,
515 priority: AnnotationPriority,
516 message: impl Into<Cow<'a, str>>,
517 ) -> &mut Self {
518 let message = message.into();
519
520 if message.contains('\n') {
521 panic!("Multiline annotation messages not supported.");
522 }
523
524 let span = match span.to_site_span(self.code) {
525 Some(span) => span,
526
527 None => panic!("Invalid annotation span."),
528 };
529
530 self.annotations.push(Annotation {
531 span,
532 priority,
533 message: PrintString::from_cow(message),
534 });
535
536 self
537 }
538
539 pub fn finish(&mut self) -> std::fmt::Result {
546 let (cover, mut lines) = self.scan();
549
550 let mut code_length = 0;
551
552 for print_line in &mut lines {
553 for string in &print_line.before {
554 code_length = code_length.max(string.length);
555 }
556
557 code_length = code_length.max(print_line.code.length);
558
559 for string in &print_line.after {
560 code_length = code_length.max(string.length);
561 }
562 }
563
564 let caption = match self.config.caption {
565 false => StyleString::empty(),
566 true => StyleString::from_str(self.config, self.caption.as_str()),
567 };
568
569 let summary = match self.config.summary {
570 false => Vec::new(),
571
572 true => {
573 let mut summary = Vec::with_capacity(4);
574
575 if !self.summary.is_empty() {
576 for summary_line in self.summary.as_str().lines() {
577 summary.push(StyleString::from_str(self.config, summary_line));
578 }
579 }
580
581 summary
582 }
583 };
584
585 if self.config.draw_frame && caption.length > 0 {
586 code_length = code_length.max(
587 caption.length
588 + self.config.caption_start().length
589 + self.config.caption_end().length,
590 );
591 }
592
593 if self.config.draw_frame && !self.summary.is_empty() {
594 for summary_line in &summary {
595 code_length = code_length.max(summary_line.length);
596 }
597 }
598
599 let numbers_length = (cover.end.line.checked_ilog10().unwrap_or(0) as usize + 1)
600 .max(self.config.etc().length);
601
602 let mut margin: usize = self.config.margin();
603
604 if self.config.draw_frame {
605 margin = margin
606 .checked_sub(2 + self.config.box_vertical().length * 2)
607 .unwrap_or_default();
608 }
609
610 if self.config.show_numbers {
611 margin = margin.checked_sub(numbers_length + 2).unwrap_or_default();
612 }
613
614 code_length = code_length.max(margin);
615
616 let dim = !self.annotations.is_empty();
619 let has_caption = caption.length > 0;
620 let has_summary = !summary.is_empty();
621 let mut is_first = true;
622
623 if self.config.draw_frame || has_caption || has_summary {
624 StyleString::start(is_first)
625 .with_header_blank(self.config, numbers_length)
626 .with_caption(self.config, code_length, caption)
627 .end(&mut is_first, self.formatter)?;
628 }
629
630 let mut back_distance: usize = 0;
631 let mut skip = false;
632 let mut distances = Vec::with_capacity(lines.len());
633
634 for line in lines.iter().rev() {
635 match line.annotated {
636 false => back_distance += 1,
637 true => back_distance = 0,
638 }
639
640 distances.push(back_distance);
641 }
642
643 back_distance = 0;
644
645 for (forward_distance, line) in distances.into_iter().rev().zip(lines) {
646 if line.annotated || !self.config.show_numbers || !dim {
647 back_distance = 0;
648 skip = false;
649
650 for string in line.before {
651 StyleString::start(is_first)
652 .with_header_blank(self.config, numbers_length)
653 .with_code(
654 self.config,
655 dim,
656 has_caption,
657 has_summary,
658 code_length,
659 string,
660 )
661 .end(&mut is_first, self.formatter)?;
662 }
663
664 StyleString::start(is_first)
665 .with_header_number(self.config, numbers_length, line.number)
666 .with_code(
667 self.config,
668 dim,
669 has_caption,
670 has_summary,
671 code_length,
672 line.code,
673 )
674 .end(&mut is_first, self.formatter)?;
675
676 for string in line.after {
677 StyleString::start(is_first)
678 .with_header_blank(self.config, numbers_length)
679 .with_code(
680 self.config,
681 dim,
682 has_caption,
683 has_summary,
684 code_length,
685 string,
686 )
687 .end(&mut is_first, self.formatter)?;
688 }
689
690 continue;
691 }
692
693 back_distance += 1;
694
695 let min_distance = forward_distance.min(back_distance);
696
697 if skip {
698 match min_distance <= self.config.cover() {
699 true => skip = false,
700 false => continue,
701 }
702 }
703
704 if min_distance > self.config.cover() {
705 if forward_distance >= self.config.continuation() {
706 StyleString::start(is_first)
707 .with_header_etc(self.config, numbers_length)
708 .with_code_blank(self.config, dim, has_caption, has_summary, code_length)
709 .end(&mut is_first, self.formatter)?;
710 skip = true;
711 continue;
712 }
713 }
714
715 StyleString::start(is_first)
716 .with_header_number(self.config, numbers_length, line.number)
717 .with_code(
718 self.config,
719 dim,
720 has_caption,
721 has_summary,
722 code_length,
723 line.code,
724 )
725 .end(&mut is_first, self.formatter)?;
726 }
727
728 if has_summary {
729 StyleString::start(is_first)
730 .with_header_blank(self.config, numbers_length)
731 .with_delimiter(self.config, code_length)
732 .end(&mut is_first, self.formatter)?;
733
734 for summary in summary {
735 StyleString::start(is_first)
736 .with_header_blank(self.config, numbers_length)
737 .with_summary(self.config, code_length, summary)
738 .end(&mut is_first, self.formatter)?;
739 }
740 }
741
742 if self.config.draw_frame || has_caption || has_summary {
743 StyleString::start(is_first)
744 .with_header_blank(self.config, numbers_length)
745 .with_footer(self.config, code_length)
746 .end(&mut is_first, self.formatter)?;
747 }
748
749 Ok(())
750 }
751
752 fn scan(&mut self) -> (PositionSpan, Vec<ScanLine>) {
753 struct Scanner {
754 position_cover: PositionSpan,
755 site_cover: SiteSpan,
756 buffer: Vec<ScanLine>,
757 empty: bool,
758 line: Line,
759 pending: ScanLine,
760 stack: Vec<usize>,
761 }
762
763 impl Scanner {
764 fn new<C: SourceCode>(snippet: &Snippet<C>) -> Self {
765 let position_cover = snippet
766 .annotations
767 .iter()
768 .map(|annotation| annotation.span.clone())
769 .reduce(|a, b| a.start.min(b.start)..a.end.max(b.end))
770 .map(|cover| {
771 let mut cover = match cover.to_position_span(snippet.code) {
772 Some(span) => span,
773
774 None => unsafe { ld_unreachable!("Invalid site span.") },
776 };
777
778 cover.start.line = cover
779 .start
780 .line
781 .checked_sub(snippet.config.cover())
782 .unwrap_or(1)
783 .max(1);
784
785 cover.start.column = 1;
786
787 cover.end.line = cover
788 .end
789 .line
790 .checked_add(snippet.config.cover())
791 .unwrap_or(usize::MAX);
792 cover.end.column = Column::MAX;
793
794 cover
795 })
796 .unwrap_or_else(|| {
797 let end = match Site::MAX.to_position(snippet.code) {
798 Some(mut position) => {
799 position.column = usize::MAX;
800
801 position
802 }
803
804 None => unsafe { ld_unreachable!("Invalid end site.") },
806 };
807
808 Position::default()..end
809 });
810
811 let buffer =
812 Vec::with_capacity(position_cover.end.line - position_cover.start.line + 1);
813 let line = position_cover.start.line;
814 let pending = ScanLine::new(line);
815 let stack = Vec::with_capacity(snippet.annotations.len());
816 let site_cover = match position_cover.to_site_span(snippet.code) {
817 Some(span) => span,
818 None => unsafe { ld_unreachable!("Invalid position span.") },
820 };
821
822 Self {
823 position_cover,
824 site_cover,
825 buffer,
826 empty: true,
827 line,
828 pending,
829 stack,
830 }
831 }
832
833 #[inline(always)]
834 fn submit(&mut self, config: &SnippetConfig) {
835 self.line += 1;
836
837 let mut pending = replace(&mut self.pending, ScanLine::new(self.line));
838
839 pending.expand(config);
840
841 pending
842 .messages
843 .sort_by_key(|message| message.priority.order());
844
845 self.buffer.push(pending);
846 }
847
848 #[inline(always)]
849 fn top(&self) -> Option<usize> {
850 self.stack.last().copied()
851 }
852 }
853
854 let mut scanner = Scanner::new(self);
855
856 let dim = !self.annotations.is_empty();
857
858 let code_style = self.config.code_style(dim);
859 let mut token_style = None;
860
861 'chunk_loop: for chunk in self.code.chunks(&scanner.site_cover) {
862 let mut site = chunk.site;
863
864 if self.config.style {
865 if let Some(highlighter) = &mut self.highlighter {
866 token_style = highlighter.token_style(dim, chunk.token);
867 }
868 }
869
870 for ch in chunk.string.chars() {
871 if site < scanner.site_cover.start {
872 site += 1;
873 continue;
874 }
875
876 for (index, annotation) in self.annotations.iter().enumerate() {
877 if annotation.span.end != site {
878 continue;
879 }
880
881 scanner.stack.retain(|item| *item != index);
882 }
883
884 for (index, annotation) in self.annotations.iter().enumerate() {
885 if annotation.span.start != site {
886 continue;
887 }
888
889 if !annotation.message.is_empty() {
890 scanner
891 .pending
892 .messages
893 .push(annotation.message(self.config, scanner.pending.code.length));
894 }
895
896 match annotation.span.end == site {
897 true => {
898 scanner.pending.code.style =
899 self.config.annotation_style(annotation.priority);
900 scanner.pending.code.write_placeholder(self.config);
901 scanner.pending.annotated = true;
902 }
903
904 false => {
905 if ch == '\n' {
906 scanner.pending.code.style =
907 self.config.annotation_style(annotation.priority);
908 scanner.pending.code.write_placeholder(self.config);
909 }
910
911 scanner.stack.push(index);
912 }
913 }
914 }
915
916 scanner.pending.code.style = match scanner.top() {
917 None => token_style.unwrap_or(code_style),
918
919 Some(top) => {
920 let priority = match self.annotations.get(top) {
921 Some(annotation) => annotation.priority,
922
923 None => unsafe { ld_unreachable!("Missing annotation.") },
925 };
926
927 scanner.pending.annotated = true;
928
929 self.config.annotation_style(priority)
930 }
931 };
932
933 scanner.empty = false;
934
935 match ch {
936 '\n' => scanner.submit(self.config),
937 '\t' => scanner.pending.code.write_tab(self.config),
938 _ => scanner.pending.code.write_code_char(self.config, ch),
939 }
940
941 site += 1;
942
943 if site >= scanner.site_cover.end {
944 break 'chunk_loop;
945 }
946 }
947 }
948
949 for annotation in self.annotations.iter() {
950 if annotation.span.start != scanner.site_cover.end {
951 continue;
952 }
953
954 if !annotation.span.is_empty() {
955 continue;
956 }
957
958 if !annotation.message.is_empty() {
959 scanner
960 .pending
961 .messages
962 .push(annotation.message(self.config, scanner.pending.code.length));
963 }
964
965 scanner.pending.annotated = true;
966 scanner.pending.code.style = self.config.annotation_style(annotation.priority);
967 scanner.pending.code.write_placeholder(self.config);
968
969 scanner.empty = false;
970 }
971
972 if !scanner.empty {
973 scanner.submit(self.config);
974 }
975
976 (scanner.position_cover, scanner.buffer)
977 }
978}
979
980pub trait Highlighter<T: Token> {
988 fn token_style(&mut self, dim: bool, token: T) -> Option<Style>;
1003}
1004
1005#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)]
1007pub enum AnnotationPriority {
1008 #[default]
1010 Default,
1011
1012 Primary,
1014
1015 Secondary,
1017
1018 Note,
1020}
1021
1022impl AnnotationPriority {
1023 #[inline(always)]
1024 fn order(&self) -> usize {
1025 match self {
1026 Self::Primary => 1,
1027 Self::Secondary => 2,
1028 Self::Note => 3,
1029 Self::Default => 4,
1030 }
1031 }
1032}
1033
1034struct ScanLine {
1035 number: Line,
1036 before: Vec<StyleString>,
1037 code: StyleString,
1038 after: Vec<StyleString>,
1039 messages: Vec<Message>,
1040 annotated: bool,
1041}
1042
1043impl ScanLine {
1044 #[inline(always)]
1045 fn new(number: Line) -> Self {
1046 Self {
1047 number,
1048 before: Vec::new(),
1049 code: StyleString::new(),
1050 after: Vec::new(),
1051 messages: Vec::new(),
1052 annotated: false,
1053 }
1054 }
1055
1056 fn expand(&mut self, config: &SnippetConfig) {
1057 enum Segment {
1058 End(Message),
1059 Middle {
1060 offset: Column,
1061 priority: AnnotationPriority,
1062 },
1063 }
1064
1065 impl Segment {
1066 #[inline(always)]
1067 fn span(&self, config: &SnippetConfig) -> SiteSpan {
1068 match self {
1069 Self::Middle { offset, .. } => Message::span_down_middle(config, *offset),
1070 Self::End(message) => message.span_down_right(config),
1071 }
1072 }
1073 }
1074
1075 let mut pending = take(&mut self.messages)
1076 .into_iter()
1077 .map(|message| Some(message))
1078 .collect::<Vec<_>>();
1079
1080 let mut left = pending.len();
1081
1082 if left > 1 {
1083 for message in pending.iter_mut() {
1084 if let Some(message) = message {
1085 if message.priority == AnnotationPriority::Primary {
1086 continue;
1087 }
1088 }
1089
1090 let message = match take(message) {
1091 Some(message) => message,
1092
1093 None => unsafe { ld_unreachable!("Unset first message.") },
1095 };
1096
1097 left -= 1;
1098
1099 let mut string = StyleString::new();
1100
1101 string.style = config.code_style(true);
1102 string.write_blanks(message.offset);
1103
1104 string.style = config.annotation_style(message.priority).no_emphasis();
1105 string.write_sanitized(config.arrow_up_right());
1106
1107 string.style = Style::new();
1108 string.append(message.string);
1109
1110 self.before.push(string);
1111
1112 break;
1113 }
1114 }
1115
1116 let mut segments = Vec::<Segment>::with_capacity(left);
1117
1118 while left > 0 {
1119 'outer: for pending in pending.iter_mut().rev() {
1120 let message = match pending {
1121 Some(pending) => pending,
1122 None => continue,
1123 };
1124
1125 let mut index = 0;
1126
1127 let span = message.span_down_right(config);
1128
1129 for probe in segments.iter() {
1130 let probe_span = probe.span(config);
1131
1132 if span.end > probe_span.start && span.start < probe_span.end {
1133 continue 'outer;
1134 }
1135
1136 if span.start >= probe_span.end {
1137 index += 1;
1138 continue;
1139 }
1140
1141 break;
1142 }
1143
1144 let segment = match take(pending) {
1145 Some(message) => Segment::End(message),
1146
1147 None => unsafe { ld_unreachable!("Missing pending item.") },
1149 };
1150
1151 left -= 1;
1152
1153 segments.insert(index, segment);
1154 }
1155
1156 'outer: for message in pending.iter().flatten() {
1157 let mut index = 0;
1158
1159 let span = Message::span_down_middle(config, message.offset);
1160
1161 for probe in segments.iter() {
1162 let probe_span = probe.span(config);
1163
1164 if span.end > probe_span.start && span.start < probe_span.end {
1165 continue 'outer;
1166 }
1167
1168 if span.start >= probe_span.end {
1169 index += 1;
1170 continue;
1171 }
1172
1173 break;
1174 }
1175
1176 segments.insert(
1177 index,
1178 Segment::Middle {
1179 offset: message.offset,
1180 priority: message.priority,
1181 },
1182 );
1183 }
1184
1185 let mut string = StyleString::new();
1186
1187 let mut cursor = 0;
1188 for segment in replace(&mut segments, Vec::with_capacity(left)) {
1189 match segment {
1190 Segment::Middle { offset, priority } => {
1191 string.style = config.code_style(true);
1192 string.write_blanks(offset - cursor);
1193
1194 let drawing = config.arrow_down_middle();
1195
1196 string.style = config.annotation_style(priority).no_emphasis();
1197 string.write_sanitized(&drawing);
1198
1199 cursor = offset + drawing.length;
1200 }
1201
1202 Segment::End(message) => {
1203 let span = message.span_down_right(config);
1204
1205 string.style = config.code_style(true);
1206 string.write_blanks(span.start - cursor);
1207
1208 string.style = config.annotation_style(message.priority).no_emphasis();
1209 string.write_sanitized(config.arrow_down_right());
1210
1211 string.append(message.string);
1212
1213 cursor = span.end;
1214 }
1215 }
1216 }
1217
1218 self.after.push(string);
1219 }
1220 }
1221}
1222
1223struct Annotation<'a> {
1224 span: SiteSpan,
1225 priority: AnnotationPriority,
1226 message: PrintString<'a>,
1227}
1228
1229impl<'a> Annotation<'a> {
1230 #[inline(always)]
1231 fn message(&self, config: &SnippetConfig, offset: Column) -> Message {
1232 Message {
1233 offset,
1234 priority: self.priority,
1235 string: StyleString::from_str(config, self.message.as_str()),
1236 }
1237 }
1238}
1239
1240struct Message {
1241 offset: Column,
1242 priority: AnnotationPriority,
1243 string: StyleString,
1244}
1245
1246impl Message {
1247 #[inline(always)]
1248 #[allow(unused)]
1249 fn span_up_right(&self, config: &SnippetConfig) -> SiteSpan {
1250 let drawing = config.arrow_up_right();
1251
1252 self.offset..(self.offset + drawing.length + self.string.length)
1253 }
1254
1255 #[inline(always)]
1256 fn span_down_right(&self, config: &SnippetConfig) -> SiteSpan {
1257 let drawing = config.arrow_down_right();
1258
1259 self.offset..(self.offset + drawing.length + self.string.length)
1260 }
1261
1262 #[inline(always)]
1263 fn span_down_middle(config: &SnippetConfig, offset: Column) -> SiteSpan {
1264 let drawing = config.arrow_down_middle();
1265
1266 offset..(offset + drawing.length)
1267 }
1268}
1269
1270struct StyleString {
1271 text: String,
1272 length: Length,
1273 start_style: Style,
1274 end_style: Style,
1275 style: Style,
1276}
1277
1278impl Display for StyleString {
1279 #[inline(always)]
1280 fn fmt(&self, formatter: &mut Formatter) -> std::fmt::Result {
1281 formatter.write_str(&self.text)
1282 }
1283}
1284
1285impl StyleString {
1286 #[inline(always)]
1287 fn new() -> Self {
1288 Self {
1289 text: String::with_capacity(120),
1290 length: 0,
1291 start_style: Style::new(),
1292 end_style: Style::new(),
1293 style: Style::new(),
1294 }
1295 }
1296
1297 #[inline(always)]
1298 fn empty() -> Self {
1299 Self {
1300 text: String::new(),
1301 length: 0,
1302 start_style: Style::new(),
1303 end_style: Style::new(),
1304 style: Style::new(),
1305 }
1306 }
1307
1308 fn from_str(config: &SnippetConfig, source: impl AsRef<str>) -> Self {
1309 let source = source.as_ref();
1310
1311 let mut target = Self::new();
1312
1313 let buffer = TokenBuffer::from(source);
1314
1315 for chunk in buffer.chunks(..) {
1316 match chunk.token {
1317 Escaped::CSI => {
1318 if !config.style {
1319 continue;
1320 }
1321 }
1322 _ => target.length += chunk.length,
1323 }
1324
1325 target.text.push_str(chunk.string);
1326 }
1327
1328 target
1329 }
1330
1331 #[inline]
1332 fn start(is_first: bool) -> Self {
1333 let mut string = Self::new();
1334
1335 if !is_first {
1336 string.text.push('\n');
1337 }
1338
1339 string
1340 }
1341
1342 fn with_header_blank(self, config: &SnippetConfig, alignment: Length) -> Self {
1343 self.with_header(config, alignment, "")
1344 }
1345
1346 fn with_header_etc(self, config: &SnippetConfig, alignment: Length) -> Self {
1347 self.with_header(config, alignment, config.etc().as_str())
1348 }
1349
1350 fn with_header_number(self, config: &SnippetConfig, alignment: Length, number: Line) -> Self {
1351 self.with_header(config, alignment, number.to_string().as_str())
1352 }
1353
1354 #[inline]
1355 fn with_header(mut self, config: &SnippetConfig, alignment: Length, text: &str) -> Self {
1356 if !config.show_numbers {
1357 return self;
1358 }
1359
1360 self.write_blanks(1);
1361 self.write_sanitized(&PrintString::owned(format!("{: >1$}", text, alignment)));
1362 self.write_blanks(1);
1363
1364 self
1365 }
1366
1367 fn with_caption(
1368 mut self,
1369 config: &SnippetConfig,
1370 mut alignment: Length,
1371 caption: Self,
1372 ) -> Self {
1373 self.write_sanitized(config.box_top_left());
1374 self.write_sanitized(config.box_horizontal());
1375
1376 let has_caption = caption.length > 0;
1377
1378 if has_caption {
1379 alignment -= config.caption_start().length;
1380 self.write_sanitized(config.caption_start());
1381
1382 alignment -= caption.length;
1383 self.append(caption);
1384
1385 alignment -= config.caption_end().length;
1386 self.write_sanitized(config.caption_end());
1387 }
1388
1389 match config.draw_frame {
1390 true => {
1391 self.repeat_sanitized(config.box_horizontal(), alignment + 1);
1392 self.write_sanitized(config.box_top_right());
1393 }
1394
1395 false => {
1396 if !has_caption {
1397 self.write_sanitized(config.box_horizontal());
1398 }
1399 }
1400 }
1401
1402 self
1403 }
1404
1405 fn with_code(
1406 mut self,
1407 config: &SnippetConfig,
1408 dim: bool,
1409 has_caption: bool,
1410 has_summary: bool,
1411 mut alignment: Length,
1412 code: Self,
1413 ) -> Self {
1414 let code_style = config.code_style(dim);
1415
1416 if config.draw_frame || config.show_numbers || has_caption || has_summary {
1417 self.write_sanitized(config.box_vertical());
1418 self.style = code_style;
1419 self.write_blanks(1);
1420 }
1421
1422 alignment -= code.length;
1423 self.append(code);
1424
1425 if config.draw_frame {
1426 self.style = code_style;
1427 self.write_blanks(alignment + 1);
1428
1429 self.style = Style::new();
1430 self.write_sanitized(config.box_vertical());
1431 }
1432
1433 self
1434 }
1435
1436 fn with_code_blank(
1437 mut self,
1438 config: &SnippetConfig,
1439 dim: bool,
1440 has_caption: bool,
1441 has_summary: bool,
1442 alignment: Length,
1443 ) -> Self {
1444 if config.draw_frame || config.show_numbers || has_caption || has_summary {
1445 self.write_sanitized(config.box_vertical());
1446 }
1447
1448 if config.draw_frame {
1449 self.style = config.code_style(dim);
1450 self.write_blanks(alignment + 2);
1451
1452 self.style = Style::new();
1453 self.write_sanitized(config.box_vertical());
1454 }
1455
1456 if self.length == 0 {
1457 self.length = 1;
1458 }
1459
1460 self
1461 }
1462
1463 fn with_delimiter(mut self, config: &SnippetConfig, alignment: Length) -> Self {
1464 match config.draw_frame {
1465 true => {
1466 self.write_sanitized(config.box_middle_left());
1467 self.repeat_sanitized(config.box_horizontal(), alignment + 2);
1468 self.write_sanitized(config.box_middle_right());
1469 }
1470
1471 false => {
1472 self.write_sanitized(config.box_middle_delimiter());
1473 }
1474 }
1475
1476 self
1477 }
1478
1479 fn with_summary(
1480 mut self,
1481 config: &SnippetConfig,
1482 mut alignment: Length,
1483 summary: Self,
1484 ) -> Self {
1485 self.write_sanitized(config.box_vertical());
1486 self.write_blanks(1);
1487
1488 alignment -= summary.length;
1489 self.append(summary);
1490
1491 if config.draw_frame {
1492 self.write_blanks(alignment + 1);
1493 self.write_sanitized(config.box_vertical());
1494 }
1495
1496 self
1497 }
1498
1499 fn with_footer(mut self, config: &SnippetConfig, alignment: Length) -> Self {
1500 self.write_sanitized(config.box_bottom_left());
1501 self.write_sanitized(config.box_horizontal());
1502
1503 match config.draw_frame {
1504 true => {
1505 self.repeat_sanitized(config.box_horizontal(), alignment + 1);
1506 self.write_sanitized(config.box_bottom_right());
1507 }
1508
1509 false => {
1510 self.write_sanitized(config.box_horizontal());
1511 }
1512 }
1513
1514 self
1515 }
1516
1517 #[inline]
1518 fn end(mut self, is_first: &mut bool, formatter: &mut Formatter) -> std::fmt::Result {
1519 if self.length == 0 {
1520 return Ok(());
1521 }
1522
1523 *is_first = false;
1524
1525 self.style = Style::new();
1526 self.submit_style();
1527
1528 Display::fmt(&self, formatter)
1529 }
1530
1531 #[inline(always)]
1532 fn write_code_char(&mut self, config: &SnippetConfig, mut ch: char) {
1533 if ch.is_control() {
1534 ch = config.control();
1535 }
1536
1537 self.submit_style();
1538
1539 self.text.push(ch);
1540 self.length += 1;
1541 }
1542
1543 #[inline(always)]
1544 fn write_sanitized(&mut self, string: &PrintString) {
1545 self.submit_style();
1546
1547 self.text.push_str(string.as_str());
1548 self.length += string.length;
1549 }
1550
1551 #[inline(always)]
1552 fn repeat_sanitized(&mut self, string: &PrintString, mut count: usize) {
1553 if count == 0 {
1554 return;
1555 }
1556
1557 self.submit_style();
1558
1559 while count > 0 {
1560 self.text.push_str(string.as_str());
1561 self.length += string.length;
1562 count -= 1;
1563 }
1564 }
1565
1566 #[inline(always)]
1567 fn write_placeholder(&mut self, config: &SnippetConfig) {
1568 self.submit_style();
1569
1570 self.text.push(config.placeholder());
1571 self.length += 1;
1572 }
1573
1574 #[inline(always)]
1575 fn write_tab(&mut self, config: &SnippetConfig) {
1576 self.write_sanitized(config.tab());
1577 }
1578
1579 #[inline(always)]
1580 fn write_blanks(&mut self, count: Length) {
1581 if count == 0 {
1582 return;
1583 }
1584
1585 self.submit_style();
1586
1587 self.text.extend(repeat(' ').take(count));
1588 self.length += count;
1589 }
1590
1591 fn append(&mut self, other: StyleString) {
1592 self.style = other.start_style;
1593
1594 if !other.text.is_empty() {
1595 self.submit_style();
1596 self.text.push_str(other.text.as_str());
1597 }
1598
1599 self.length += other.length;
1600
1601 self.end_style = other.end_style;
1602 self.style = other.style;
1603 }
1604
1605 fn submit_style(&mut self) {
1606 if self.end_style == self.style {
1607 return;
1608 }
1609
1610 Style::change(&self.end_style, &self.style, &mut self.text);
1611
1612 self.end_style = self.style;
1613
1614 if self.length == 0 {
1615 self.start_style = self.end_style;
1616 }
1617 }
1618}
1619
1620struct PrintString<'a> {
1621 string: Cow<'a, str>,
1622 length: Length,
1623}
1624
1625impl<'a> PrintString<'a> {
1626 #[inline(always)]
1627 const fn empty() -> Self {
1628 Self {
1629 string: Cow::Borrowed(""),
1630 length: 0,
1631 }
1632 }
1633
1634 #[inline(always)]
1635 fn owned(string: String) -> Self {
1636 Self {
1637 length: string.chars().count(),
1638 string: Cow::from(string),
1639 }
1640 }
1641
1642 #[inline(always)]
1643 const fn borrowed(string: &'a str) -> Self {
1644 Self {
1645 length: Self::length_of(string.as_bytes()),
1646 string: Cow::Borrowed(string),
1647 }
1648 }
1649
1650 #[inline(always)]
1651 fn from_cow(string: Cow<'a, str>) -> Self {
1652 Self {
1653 length: string.chars().count(),
1654 string,
1655 }
1656 }
1657
1658 #[inline(always)]
1659 fn as_str(&self) -> &str {
1660 self.string.as_ref()
1661 }
1662
1663 #[inline(always)]
1664 fn is_empty(&self) -> bool {
1665 self.string.is_empty()
1666 }
1667
1668 #[inline(always)]
1669 const fn length_of(bytes: &[u8]) -> Length {
1670 const PAT_1: u8 = 0b10000000;
1671 const PAT_3: u8 = 0b11100000;
1672 const PAT_4: u8 = 0b11110000;
1673
1674 let mut index = 0;
1675 let mut length = 0;
1676
1677 while index < bytes.len() {
1678 length += 1;
1679
1680 let first = bytes[index];
1681
1682 if first & PAT_1 == 0 {
1683 index += 1;
1684 continue;
1685 }
1686
1687 let prefix = first & PAT_4;
1688
1689 match prefix {
1690 PAT_4 => index += 4,
1691 PAT_3 => index += 3,
1692 _ => index += 2,
1693 }
1694 }
1695
1696 length
1697 }
1698}
1699
1700#[cfg(test)]
1701mod tests {
1702 use crate::format::{snippet::StyleString, SnippetConfig, Style, TerminalString};
1703
1704 #[test]
1705 fn test_csi_detection() {
1706 let string = StyleString::from_str(&SnippetConfig::verbose(), "hello world");
1707 assert_eq!(string.length, 11);
1708
1709 let string = StyleString::from_str(
1710 &SnippetConfig::verbose(),
1711 &format!("hello{}world", " ".apply(Style::new())),
1712 );
1713 assert_eq!(string.length, 11);
1714 assert_eq!(string.text.len(), 11);
1715
1716 let string = StyleString::from_str(
1717 &SnippetConfig::verbose(),
1718 &format!("hello{}world", " ".apply(Style::new().bold())),
1719 );
1720 assert_eq!(string.length, 11);
1721 assert_ne!(string.text.len(), 11);
1722
1723 let string = StyleString::from_str(
1724 &SnippetConfig::minimal(),
1725 &format!("hello{}world", " ".apply(Style::new().bold())),
1726 );
1727 assert_eq!(string.length, 11);
1728 assert_eq!(string.text.len(), 11);
1729 }
1730}