1#![no_std]
2#![forbid(unsafe_code)]
3#![warn(missing_docs)]
4extern crate alloc;
133
134use alloc::vec::Vec;
135use core::fmt::{self, Display, Formatter};
136use core::ops::Range;
137
138pub struct LineIndex<'a>(Vec<(usize, &'a str)>);
143
144impl<'a> LineIndex<'a> {
145 #[must_use]
147 pub fn new(s: &'a str) -> Self {
148 let newlines: Vec<_> = s
150 .char_indices()
151 .filter_map(|(i, c)| (c == '\n').then_some(i))
152 .collect();
153 let starts = core::iter::once(0).chain(newlines.iter().map(|i| *i + 1));
155 let ends = newlines.iter().copied().chain(core::iter::once(s.len()));
156
157 let lines = starts.zip(ends).map(|(start, end)| (start, &s[start..end]));
158 Self(lines.collect())
159 }
160
161 fn get(&self, offset: usize) -> Option<IndexEntry<'_>> {
162 use core::cmp::Ordering;
163 let line_no = self.0.binary_search_by(|(line_start, line)| {
164 if *line_start > offset {
165 Ordering::Greater
166 } else if line_start + line.len() < offset {
167 Ordering::Less
168 } else {
169 Ordering::Equal
170 }
171 });
172 let line_no = line_no.ok()?;
173 let (line_start, line) = self.0[line_no];
174 Some(IndexEntry {
175 line_no,
176 line,
177 bytes: offset - line_start,
178 })
179 }
180}
181
182struct IndexEntry<'a> {
183 line: &'a str,
184 line_no: usize,
185 bytes: usize,
187}
188
189struct Fns {
191 snake: Option<fn(&mut Formatter, usize) -> fmt::Result>,
193 text: fn(&mut Formatter, usize, usize) -> fmt::Result,
195}
196
197impl<T> LabelKind<T> {
198 fn has_snake(&self) -> bool {
199 match self {
200 Self::None => false,
201 Self::Snake | Self::Text(_) => true,
202 }
203 }
204
205 fn text(self) -> Option<T> {
206 match self {
207 Self::None | Self::Snake => None,
208 Self::Text(t) => Some(t),
209 }
210 }
211}
212
213pub struct Label<C, T, S = ()> {
215 code: C,
216 kind: LabelKind<T>,
217 style: S,
218}
219
220impl<T, S: Default> Label<Range<usize>, T, S> {
221 #[must_use]
227 pub fn new(code: Range<usize>) -> Self {
228 Self {
229 code,
230 kind: LabelKind::None,
231 style: S::default(),
232 }
233 }
234}
235
236impl<C, T, S> Label<C, T, S> {
237 #[must_use]
239 pub fn with_text(self, text: T) -> Self {
240 let kind = LabelKind::Text(text);
241 Self { kind, ..self }
242 }
243
244 #[must_use]
246 pub fn with_snake(self) -> Self {
247 let kind = LabelKind::Snake;
248 Self { kind, ..self }
249 }
250
251 #[must_use]
253 pub fn with_style(self, style: S) -> Self {
254 Self { style, ..self }
255 }
256}
257
258pub struct CodeWidth<C> {
260 code: C,
261 width: usize,
262}
263
264impl<C> CodeWidth<C> {
265 pub fn new(code: C, width: usize) -> Self {
267 CodeWidth { code, width }
268 }
269
270 fn left_right(&self) -> (usize, usize) {
272 let left = self.width / 2;
273 let right = self.width.saturating_sub(left + 1);
274 (left, right)
275 }
276}
277
278impl<C: Display> Display for CodeWidth<C> {
279 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
280 self.code.fmt(f)
281 }
282}
283
284type Paint<S> = fn(&mut Formatter, &S, &dyn Display) -> fmt::Result;
285
286fn styled<'a, S>(paint: Paint<S>, style: &'a S, x: &'a impl Display) -> impl Display + 'a {
287 from_fn(move |f| paint(f, style, x))
288}
289
290struct FromFn<F>(F);
291
292fn from_fn<F: Fn(&mut Formatter) -> fmt::Result>(f: F) -> FromFn<F> {
293 FromFn(f)
294}
295
296impl<F: Fn(&mut Formatter) -> fmt::Result> Display for FromFn<F> {
297 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
298 (self.0)(f)
299 }
300}
301
302enum LabelKind<T> {
303 None,
304 Snake,
305 Text(T),
306}
307
308pub struct Block<C, T, S> {
310 lines: Vec<Option<Line<C, T, S>>>,
311 paint: Paint<S>,
312}
313
314struct Line<C, T, S> {
315 no: usize,
316 parts: LineParts<C, T, S>,
317}
318
319impl<C, T, S> Line<C, T, S> {
320 fn map_code<C1>(self, f: impl FnMut(C) -> C1) -> Line<C1, T, S> {
321 Line {
322 no: self.no,
323 parts: self.parts.map_code(f),
324 }
325 }
326}
327
328struct LineParts<C, T, S> {
330 incoming: Option<(C, Option<T>, S)>,
332 inside: Vec<(C, LabelKind<T>, S)>,
333 outgoing: Option<(C, S)>,
335}
336
337impl<C, T, S> LineParts<C, T, S> {
338 fn arrows_below(&self) -> bool {
339 let inside = |(_code, label, _style): &_| LabelKind::has_snake(label);
340 self.incoming.is_some() || self.outgoing.is_some() || self.inside.iter().any(inside)
341 }
342}
343
344impl<C, T, S> Default for LineParts<C, T, S> {
345 fn default() -> Self {
346 Self {
347 incoming: None,
348 inside: Vec::new(),
349 outgoing: None,
350 }
351 }
352}
353
354impl<'a, T, S: Default + Clone> Block<&'a str, T, S> {
355 pub fn new<I>(idx: &'a LineIndex, labels: I) -> Option<Self>
369 where
370 I: IntoIterator<Item = Label<Range<usize>, T, S>>,
371 {
372 let mut prev_range: Option<Range<_>> = None;
373 let mut lines = Vec::new();
374 for Label { kind, code, style } in labels {
375 if code.start > code.end {
376 return None;
377 }
378 if let Some(prev) = prev_range.replace(code.clone()) {
379 if code.start <= prev.start || code.start < prev.end {
380 return None;
381 }
382 }
383 let start = idx.get(code.start)?;
384 let end = idx.get(code.end)?;
385 debug_assert!(start.line_no <= end.line_no);
386
387 let mut parts = match lines.pop() {
388 Some(Some((line_no, _line, parts))) if line_no == start.line_no => parts,
389 Some(line) => {
390 let non_consecutive = line
391 .as_ref()
392 .filter(|(no, ..)| start.line_no > no + 1)
393 .is_some();
394
395 lines.push(line);
396 if non_consecutive {
397 lines.push(None);
398 }
399 LineParts::default()
400 }
401 None => LineParts::default(),
402 };
403
404 if start.line_no == end.line_no {
405 parts.inside.push((start.bytes..end.bytes, kind, style));
406 lines.push(Some((start.line_no, start.line, parts)));
407 } else {
408 let range = start.bytes..start.line.len();
409 if kind.has_snake() {
410 parts.outgoing = Some((range, style.clone()));
411 } else {
412 parts.inside.push((range, LabelKind::None, style.clone()));
413 }
414 lines.push(Some((start.line_no, start.line, parts)));
415
416 for line_no in start.line_no + 1..end.line_no {
417 let line = idx.0[line_no].1;
418 let parts = LineParts {
419 inside: Vec::from([(0..line.len(), LabelKind::None, style.clone())]),
420 ..Default::default()
421 };
422 lines.push(Some((line_no, line, parts)));
423 }
424
425 let mut parts = LineParts::default();
426 let range = 0..end.bytes;
427 if kind.has_snake() {
428 parts.incoming = Some((range, kind.text(), style.clone()));
429 } else {
430 parts.inside.push((range, LabelKind::None, style.clone()));
431 }
432 lines.push(Some((end.line_no, end.line, parts)));
433 }
434 }
435
436 let lines = lines.into_iter().map(|line| {
437 line.map(|(no, line, parts)| Line {
438 no,
439 parts: parts.segment(line),
440 })
441 });
442 Some(Block {
443 lines: lines.collect(),
444 paint: |f, _, s| write!(f, "{s}"),
445 })
446 }
447}
448
449impl<C, T, S> Block<C, T, S> {
450 #[must_use]
452 pub fn map_code<C1>(self, mut f: impl FnMut(C) -> C1) -> Block<C1, T, S> {
453 let f = |line: Option<Line<C, T, S>>| line.map(|line| line.map_code(&mut f));
454 Block {
455 lines: self.lines.into_iter().map(f).collect(),
456 paint: self.paint,
457 }
458 }
459
460 fn some_incoming(&self) -> bool {
461 let mut lines = self.lines.iter().flatten();
462 lines.any(|line| line.parts.incoming.is_some())
463 }
464
465 fn line_no_width(&self) -> usize {
466 let max = self.lines.iter().flatten().next_back().unwrap().no + 1;
467 core::iter::successors(Some(max), |&n| (n >= 10).then_some(n / 10)).count()
469 }
470
471 #[must_use]
473 pub fn prologue(&self) -> impl Display {
474 let space = space(self.line_no_width());
475 from_fn(move |f| write!(f, "{space} {}{}", Snake::UpRight, Snake::Horizontal))
476 }
477
478 #[must_use]
482 pub fn space_vert(&self) -> impl Display {
483 let space = space(self.line_no_width());
484 from_fn(move |f| write!(f, "{space} {}", Snake::Vertical))
485 }
486
487 #[must_use]
489 pub fn epilogue(&self) -> impl Display {
490 Snake::line_up(self.line_no_width() + 1)
491 }
492}
493
494impl<C, T, S> Block<C, T, S> {
495 #[must_use]
499 pub fn with_paint(self, paint: Paint<S>) -> Self {
500 Self { paint, ..self }
501 }
502}
503
504fn space(width: usize) -> impl Display {
505 from_fn(move |f| write!(f, "{:width$}", ""))
506}
507
508macro_rules! width {
509 ($slice:expr) => {
510 $slice.iter().map(|(code, ..)| code.width).sum::<usize>()
511 };
512}
513
514impl<C: Display, T: Display, S> Display for Block<CodeWidth<C>, T, S> {
515 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
516 let paint = self.paint;
517 let mut incoming_style: Option<&S> = None;
518
519 let line_no_width = self.line_no_width();
520 let line_no_space = space(line_no_width);
521 let dots = from_fn(move |f| write!(f, "{line_no_space} {}", Snake::VerticalDots));
523
524 let incoming_space = if self.some_incoming() { " " } else { "" };
525
526 for line in &self.lines {
527 let Line { no: line_no, parts } = if let Some(line) = line {
528 line
529 } else {
530 writeln!(f, "{dots}")?;
531 continue;
532 };
533
534 write!(f, "{:>line_no_width$} │", line_no + 1)?;
536
537 if let Some(style) = incoming_style {
538 write!(f, " {}", styled(paint, style, &Snake::Vertical))?;
539 } else {
540 incoming_space.fmt(f)?;
541 }
542
543 write!(f, " ")?;
544 parts.code_parts().try_for_each(|(c, s)| paint(f, s, c))?;
545 writeln!(f)?;
546
547 if parts.arrows_below() {
550 write!(f, "{dots} ")?;
551 if let Some(style) = incoming_style {
552 styled(paint, style, &Snake::Vertical).fmt(f)?;
553 parts.incoming(paint, Snake::ArrowUp).fmt(f)?;
554 } else {
555 incoming_space.fmt(f)?;
556 }
557 writeln!(f, "{}", parts.arrows(paint))?;
558 }
559
560 if parts.incoming.is_some() {
561 assert!(incoming_style.take().is_some());
562 parts.incoming_text(&dots, paint).fmt(f)?;
563 }
564
565 let incoming_width = width!(parts.incoming);
566 let prefix = from_fn(|f| write!(f, "{dots}{incoming_space} {}", space(incoming_width)));
567 parts.inside_text(&prefix, paint).fmt(f)?;
568
569 if let Some((_, style)) = &parts.outgoing {
571 let snake = Snake::up_line_up(incoming_width + width!(parts.inside) + 1);
572 writeln!(f, "{dots} {}", styled(paint, style, &snake))?;
573
574 incoming_style = Some(style);
575 }
576 }
577 Ok(())
578 }
579}
580
581impl<C: Display, T, S> LineParts<C, T, S> {
582 fn code_parts(&self) -> impl Iterator<Item = (&C, &S)> {
583 let inside = self.inside.iter().map(|(code, _label, styl)| (code, styl));
584 let incoming = self.incoming.iter().map(|(code, _text, styl)| (code, styl));
585 let outgoing = self.outgoing.iter().map(|(code, styl)| (code, styl));
586 incoming.chain(inside).chain(outgoing)
587 }
588}
589
590impl<T, S: Default> LineParts<Range<usize>, T, S> {
591 fn segment(self, line: &str) -> LineParts<&str, T, S> {
592 let len = line.len();
593 let start = self.incoming.as_ref().map_or(0, |(code, ..)| code.end);
594 let end = self.outgoing.as_ref().map_or(len, |(code, _)| code.start);
595 let last = self.inside.last().map_or(start, |(code, ..)| code.end);
596
597 let mut pos = start;
598 let unlabelled =
599 |start, end| (start < end).then(|| (&line[start..end], LabelKind::None, S::default()));
600 let inside = self.inside.into_iter().flat_map(|(code, label, style)| {
601 let unlabelled = unlabelled(pos, code.start);
602 let labelled = (&line[code.start..code.end], label, style);
603 pos = code.end;
604 unlabelled.into_iter().chain([labelled])
605 });
606 LineParts {
607 incoming: self
608 .incoming
609 .map(|(code, text, sty)| (&line[..code.end], text, sty)),
610 inside: inside.chain(unlabelled(last, end)).collect(),
611 outgoing: self.outgoing.map(|(code, sty)| (&line[code.start..], sty)),
612 }
613 }
614}
615
616impl<C, T, S> LineParts<C, T, S> {
617 #[must_use]
618 fn map_code<C1>(self, mut f: impl FnMut(C) -> C1) -> LineParts<C1, T, S> {
619 let inside = self.inside.into_iter();
620 LineParts {
621 incoming: self.incoming.map(|(code, txt, sty)| (f(code), txt, sty)),
622 inside: inside.map(|(code, lbl, sty)| (f(code), lbl, sty)).collect(),
623 outgoing: self.outgoing.map(|(code, style)| (f(code), style)),
624 }
625 }
626}
627
628impl<C, T, S> LineParts<CodeWidth<C>, T, S> {
629 fn width(&self) -> usize {
631 width!(self.inside) + width!(self.incoming) + width!(self.outgoing)
632 }
633
634 fn incoming<'a>(&'a self, paint: Paint<S>, snake: Snake) -> impl Display + 'a {
635 from_fn(move |f| {
636 if let Some((code, _text, style)) = &self.incoming {
637 space(code.width).fmt(f)?;
638 paint(f, style, &snake)?;
639 }
640 Ok(())
641 })
642 }
643
644 fn outgoing<'a>(&'a self, paint: Paint<S>, snake: Snake) -> impl Display + 'a {
645 from_fn(move |f| {
646 if let Some((_code, style)) = &self.outgoing {
647 paint(f, style, &snake)?;
648 }
649 Ok(())
650 })
651 }
652
653 fn inside<'a>(&'a self, paint: Paint<S>, from: usize, fns: &'a Fns) -> impl Display + 'a {
654 from_fn(move |f| {
655 space(width!(self.inside[..from])).fmt(f)?;
656
657 for (code, label, style) in &self.inside[from..] {
658 match label {
659 LabelKind::Text(_text) => {
660 let (left, right) = code.left_right();
661 paint(f, style, &from_fn(|f| (fns.text)(f, left, right)))
662 }
663 LabelKind::Snake => match fns.snake {
664 Some(snake) => paint(f, style, &from_fn(|f| (snake)(f, code.width))),
665 None => space(code.width).fmt(f),
666 },
667 LabelKind::None => space(code.width).fmt(f),
668 }?;
669 }
670 Ok(())
671 })
672 }
673
674 fn arrows(&self, paint: Paint<S>) -> impl Display + '_ {
676 let fns = Fns {
677 snake: Some(|f, w| Snake::line(w).fmt(f)),
678 text: |f, l, r| Snake::line_down_line(l, r).fmt(f),
679 };
680 let outgoing = self.outgoing(paint, Snake::ArrowUp);
681 from_fn(move |f| write!(f, "{}{outgoing}", self.inside(paint, 0, &fns)))
682 }
683
684 fn inside_vert(&self, paint: Paint<S>, from: usize) -> impl Display + '_ {
686 let fns = Fns {
687 snake: None,
688 text: |f, l, r| write!(f, "{}{}{}", space(l), Snake::Vertical, space(r)),
689 };
690 let outgoing = self.outgoing(paint, Snake::Vertical);
691 from_fn(move |f| write!(f, "{}{outgoing}", self.inside(paint, from, &fns)))
692 }
693}
694
695impl<C, T: Display, S> LineParts<CodeWidth<C>, T, S> {
696 fn inside_text<'a>(&'a self, prefix: &'a impl Display, paint: Paint<S>) -> impl Display + 'a {
705 from_fn(move |f| {
706 let mut before = 0;
707 for (i, (code, label, style)) in self.inside.iter().enumerate() {
708 if let LabelKind::Text(text) = label {
709 writeln!(f, "{prefix}{}", self.inside_vert(paint, i))?;
711
712 let (left, right) = code.left_right();
714 let after = width!(self.inside) - before - code.width + width!(self.outgoing);
715 let space = space(before + left);
716 let snake = Snake::down_line(right + after + 1);
717 writeln!(f, "{prefix}{space}{} {text}", styled(paint, style, &snake))?;
718 }
719 before += code.width;
720 }
721 Ok(())
722 })
723 }
724
725 fn incoming_text<'a>(&'a self, dots: &'a impl Display, paint: Paint<S>) -> impl Display + 'a {
739 from_fn(move |f| {
740 if let Some((code, text, style)) = &self.incoming {
741 let snake = styled(paint, style, &Snake::Vertical);
742 let incoming = self.incoming(paint, Snake::Vertical);
743 writeln!(f, "{dots} {snake}{incoming}{}", self.inside_vert(paint, 0))?;
744
745 if let Some(text) = text {
746 let snake = Snake::down_line_up_line(code.width, self.width() + 1 - code.width);
747 let snake = styled(paint, style, &snake);
748 writeln!(f, "{dots} {snake} {text}")
749 } else {
750 let snake = Snake::down_line_up(code.width);
751 let snake = styled(paint, style, &snake);
752 writeln!(f, "{dots} {snake}{}", self.inside_vert(paint, 0))
753 }?
754 }
755 Ok(())
756 })
757 }
758}
759
760#[derive(Copy, Clone)]
762enum Snake {
763 Horizontal,
765 Vertical,
767 VerticalDots,
769 UpRight,
771 RightUp,
773 DownRight,
775 ArrowUp,
777 HorizontalUp,
779 HorizontalDown,
781}
782
783impl Snake {
784 fn line(len: usize) -> impl Display {
786 from_fn(move |f| write!(f, "{:─>len$}", ""))
787 }
788
789 fn down_line(len: usize) -> impl Display {
791 from_fn(move |f| write!(f, "{}{}", Snake::DownRight, Snake::line(len)))
792 }
793
794 fn down_line_up_line(l: usize, r: usize) -> impl Display {
796 let l = Self::line(l);
797 let r = Self::line(r);
798 from_fn(move |f| write!(f, "{}{l}{}{r}", Self::DownRight, Self::HorizontalUp,))
799 }
800
801 fn down_line_up(len: usize) -> impl Display {
803 from_fn(move |f| write!(f, "{}{}{}", Self::DownRight, Self::line(len), Self::RightUp))
804 }
805
806 fn up_line_up(len: usize) -> impl Display {
808 from_fn(move |f| write!(f, "{}{}{}", Self::UpRight, Self::line(len), Self::RightUp))
809 }
810
811 fn line_up(len: usize) -> impl Display {
813 from_fn(move |f| write!(f, "{}{}", Self::line(len), Self::RightUp))
814 }
815
816 fn line_down_line(l: usize, r: usize) -> impl Display {
818 let l = Self::line(l);
819 let r = Self::line(r);
820 from_fn(move |f| write!(f, "{l}{}{r}", Self::HorizontalDown,))
821 }
822
823 fn as_str(self) -> &'static str {
824 match self {
825 Self::Horizontal => "─",
826 Self::Vertical => "│",
827 Self::VerticalDots => "┆",
828 Self::UpRight => "╭",
829 Self::RightUp => "╯",
830 Self::DownRight => "╰",
831 Self::ArrowUp => "▲",
832 Self::HorizontalUp => "┴",
833 Self::HorizontalDown => "┬",
834 }
835 }
836}
837
838impl Display for Snake {
839 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
840 self.as_str().fmt(f)
841 }
842}