1#![no_std]
2#![forbid(unsafe_code)]
3#![warn(missing_docs)]
4extern crate alloc;
113
114use alloc::string::{String, ToString};
115use alloc::{boxed::Box, format, vec::Vec};
116use core::fmt::{self, Display, Formatter};
117use core::ops::Range;
118
119pub struct LineIndex<'a>(Vec<(usize, &'a str)>);
124
125impl<'a> LineIndex<'a> {
126 #[must_use]
128 pub fn new(s: &'a str) -> Self {
129 let newlines: Vec<_> = s
131 .char_indices()
132 .filter_map(|(i, c)| (c == '\n').then_some(i))
133 .collect();
134 let starts = core::iter::once(0).chain(newlines.iter().map(|i| *i + 1));
136 let ends = newlines.iter().copied().chain(core::iter::once(s.len()));
137
138 let lines = starts.zip(ends).map(|(start, end)| (start, &s[start..end]));
139 Self(lines.collect())
140 }
141
142 fn get(&self, offset: usize) -> Option<IndexEntry> {
143 use core::cmp::Ordering;
144 let line_no = self.0.binary_search_by(|(line_start, line)| {
145 if *line_start > offset {
146 Ordering::Greater
147 } else if line_start + line.len() < offset {
148 Ordering::Less
149 } else {
150 Ordering::Equal
151 }
152 });
153 let line_no = line_no.ok()?;
154 let (line_start, line) = self.0[line_no];
155 Some(IndexEntry {
156 line_no,
157 line,
158 bytes: offset - line_start,
159 })
160 }
161}
162
163#[derive(Debug)]
164struct IndexEntry<'a> {
165 line: &'a str,
166 line_no: usize,
167 bytes: usize,
168}
169
170pub struct Label<C, T> {
172 code: C,
173 text: Option<T>,
174 style: Box<Style>,
175}
176
177impl<T> Label<Range<usize>, T> {
178 #[must_use]
184 pub fn new(code: Range<usize>) -> Self {
185 Self {
186 code,
187 text: None,
188 style: Box::new(|s| s),
189 }
190 }
191}
192
193impl<C, T> Label<C, T> {
194 pub fn with_text(self, text: T) -> Self {
196 Self {
197 code: self.code,
198 text: Some(text),
199 style: self.style,
200 }
201 }
202
203 #[must_use]
205 pub fn with_style(self, style: impl Fn(String) -> String + 'static) -> Self {
206 Self {
207 code: self.code,
208 text: self.text,
209 style: Box::new(style),
210 }
211 }
212}
213
214pub struct CodeWidth<C> {
216 code: C,
217 width: usize,
218}
219
220impl<C> CodeWidth<C> {
221 pub fn new(code: C, width: usize) -> Self {
223 CodeWidth { code, width }
224 }
225
226 fn left_right(&self) -> (usize, usize) {
228 let left = self.width / 2;
229 let right = self.width.saturating_sub(left + 1);
230 (left, right)
231 }
232}
233
234impl<C: Display> Display for CodeWidth<C> {
235 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
236 self.code.fmt(f)
237 }
238}
239
240type Style = dyn Fn(String) -> String;
241
242pub struct Block<C, T>(Vec<(usize, Parts<C, T>)>);
244
245type TextStyle<T> = (Option<T>, Box<Style>);
246
247struct Parts<C, T> {
249 incoming: Option<(C, Option<T>)>,
250 inside: Vec<(C, Option<TextStyle<T>>)>,
251 outgoing: Option<(C, Box<Style>)>,
252}
253
254impl<C, T> Default for Parts<C, T> {
255 fn default() -> Self {
256 Self {
257 incoming: None,
258 inside: Vec::new(),
259 outgoing: None,
260 }
261 }
262}
263
264impl<'a, T> Block<&'a str, T> {
265 pub fn new<I>(idx: &'a LineIndex, labels: I) -> Option<Self>
279 where
280 I: IntoIterator<Item = Label<Range<usize>, T>>,
281 {
282 let mut prev_range: Option<Range<_>> = None;
283 let mut lines = Vec::new();
284 for label in labels {
285 if label.code.start > label.code.end {
286 return None;
287 }
288 if let Some(prev) = prev_range.replace(label.code.clone()) {
289 if label.code.start <= prev.start || label.code.start < prev.end {
290 return None;
291 }
292 }
293 let start = idx.get(label.code.start)?;
294 let end = idx.get(label.code.end)?;
295 debug_assert!(start.line_no <= end.line_no);
296
297 let mut parts = match lines.pop() {
298 Some((line_no, _line, parts)) if line_no == start.line_no => parts,
299 Some(line) => {
300 lines.push(line);
301 Parts::default()
302 }
303 None => Parts::default(),
304 };
305
306 if start.line_no == end.line_no {
307 let label = (label.text, label.style);
308 parts.inside.push((start.bytes..end.bytes, Some(label)));
309 lines.push((start.line_no, start.line, parts));
310 } else {
311 parts.outgoing = Some((start.bytes..start.line.len(), label.style));
312 lines.push((start.line_no, start.line, parts));
313 for line_no in start.line_no + 1..end.line_no {
314 let line = idx.0[line_no].1;
315 let parts = Parts {
316 inside: Vec::from([(0..line.len(), None)]),
317 ..Default::default()
318 };
319 lines.push((line_no, line, parts));
320 }
321 let parts = Parts {
322 incoming: Some((0..end.bytes, label.text)),
323 ..Default::default()
324 };
325 lines.push((end.line_no, end.line, parts));
326 }
327 }
328
329 let block = lines
330 .into_iter()
331 .map(|(line_no, line, parts)| (line_no, parts.segment(line)));
332 Some(Block(block.collect()))
333 }
334}
335
336pub struct Prologue(usize);
338pub struct Epilogue(usize);
340
341impl<C, T> Block<C, T> {
342 #[must_use]
344 pub fn map_code<C1>(self, mut f: impl FnMut(C) -> C1) -> Block<C1, T> {
345 let lines = self.0.into_iter();
346 let lines = lines.map(|(no, parts)| (no, parts.map_code(&mut f)));
347 Block(lines.collect())
348 }
349
350 fn some_incoming(&self) -> bool {
351 self.0.iter().any(|(.., parts)| parts.incoming.is_some())
352 }
353
354 fn line_no_width(&self) -> usize {
355 let max = self.0.last().unwrap().0 + 1;
356 core::iter::successors(Some(max), |&n| (n >= 10).then_some(n / 10)).count()
358 }
359
360 #[must_use]
362 pub fn prologue(&self) -> Prologue {
363 Prologue(self.line_no_width())
364 }
365
366 #[must_use]
368 pub fn epilogue(&self) -> Epilogue {
369 Epilogue(self.line_no_width())
370 }
371}
372
373impl Display for Prologue {
374 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
376 write!(
377 f,
378 "{} {}{}",
379 " ".repeat(self.0),
380 Snake::UpRight,
381 Snake::Horizontal
382 )
383 }
384}
385
386impl Display for Epilogue {
387 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
389 write!(f, "{}", Snake::line_up(self.0 + 1))
390 }
391}
392
393impl<C: Display, T: Display> Display for Block<CodeWidth<C>, T> {
394 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
395 let line_no_width = self.line_no_width();
396 writeln!(f, "{} {}", " ".repeat(line_no_width), Snake::Vertical)?;
398 let dots =
400 |f: &mut Formatter| write!(f, "{} {}", " ".repeat(line_no_width), Snake::VerticalDots);
401 let incoming_space =
402 |f: &mut Formatter| write!(f, "{}", if self.some_incoming() { " " } else { "" });
403
404 let mut incoming_style: Option<&Style> = None;
405
406 for (line_no, parts) in &self.0 {
407 write!(f, "{:line_no_width$} │", line_no + 1)?;
408 if let Some(style) = incoming_style {
409 write!(f, " {}", style(Snake::Vertical.to_string()))?;
410 } else {
411 incoming_space(f)?;
412 }
413
414 write!(f, " ")?;
415 parts.fmt_code(incoming_style, f)?;
416 writeln!(f)?;
417
418 dots(f)?;
419 write!(f, " ")?;
420 if let Some(style) = incoming_style {
421 style(Snake::Vertical.to_string()).fmt(f)?;
422 parts.fmt_incoming(style, Snake::ArrowUp, f)?;
423 } else {
424 incoming_space(f)?;
425 }
426 parts.fmt_arrows(f)?;
427 writeln!(f)?;
428
429 if let Some((code, text)) = &parts.incoming {
430 let style = incoming_style.take().unwrap();
431
432 dots(f)?;
433 write!(f, " ")?;
434 style(Snake::Vertical.to_string()).fmt(f)?;
435 parts.fmt_incoming(style, Snake::Vertical, f)?;
436 parts.fmt_inside_vert(0, f)?;
437 writeln!(f)?;
438
439 dots(f)?;
440 write!(f, " ")?;
441 if let Some(text) = text {
442 let snake =
443 Snake::down_line_up_line(code.width, parts.width() + 1 - code.width);
444 write!(f, "{} {}", style(snake), text)?;
445 } else {
446 let snake = Snake::down_line_up(code.width);
447 write!(f, "{}", style(snake))?;
448 parts.fmt_inside_vert(0, f)?;
449 }
450 writeln!(f)?;
451 }
452
453 let incoming_width = width(&parts.incoming);
454 let mut before = 0;
455 let prefix = |f: &mut _| {
456 dots(f)?;
457 incoming_space(f)?;
458 write!(f, " ")?;
459 " ".repeat(incoming_width).fmt(f)?;
460 Ok(())
461 };
462 for (i, (code, text_style)) in parts.inside.iter().enumerate() {
463 if let Some((Some(text), style)) = text_style {
464 prefix(f)?;
465 parts.fmt_inside_vert(i, f)?;
466 writeln!(f)?;
467
468 prefix(f)?;
469
470 let (left, right) = code.left_right();
472 let after = width(&parts.inside) - before - code.width + width(&parts.outgoing);
473 let snake = Snake::down_line(right + after + 1);
474 write!(f, "{}{} {}", " ".repeat(before + left), style(snake), text)?;
475 writeln!(f)?;
476 }
477 before += code.width;
478 }
479
480 if let Some((_, style)) = &parts.outgoing {
481 dots(f)?;
482 write!(f, " ")?;
483 style(Snake::up_line_up(incoming_width + width(&parts.inside) + 1)).fmt(f)?;
484 writeln!(f)?;
485
486 incoming_style = Some(style);
487 }
488 }
489 Ok(())
490 }
491}
492
493impl<C: Display, T> Parts<C, T> {
494 fn fmt_code(&self, mut incoming: Option<&Style>, f: &mut Formatter) -> fmt::Result {
495 if let Some((code, _text)) = &self.incoming {
496 let style = incoming.take().unwrap();
497 write!(f, "{}", style(code.to_string()))?;
498 }
499
500 for (code, label) in &self.inside {
501 match (label, incoming) {
502 (Some((_text, style)), _) => write!(f, "{}", style(code.to_string()))?,
503 (None, Some(style)) => write!(f, "{}", style(code.to_string()))?,
504 (None, None) => write!(f, "{code}")?,
505 }
506 }
507 if let Some((code, style)) = &self.outgoing {
508 write!(f, "{}", style(code.to_string()))?;
509 }
510 Ok(())
511 }
512}
513
514impl<T> Parts<Range<usize>, T> {
515 fn segment(self, line: &str) -> Parts<&str, T> {
516 let len = line.len();
517 let start = self.incoming.as_ref().map_or(0, |(code, _)| code.end);
518 let end = self.outgoing.as_ref().map_or(len, |(code, _)| code.start);
519 let last = self.inside.last().map_or(start, |(code, _)| code.end);
520
521 let mut pos = start;
522 let unlabelled = |start, end| (start < end).then(|| (&line[start..end], None));
523 let inside = self.inside.into_iter().flat_map(|(code, label)| {
524 let unlabelled = unlabelled(pos, code.start);
525 let labelled = (&line[code.start..code.end], label);
526 pos = code.end;
527 unlabelled.into_iter().chain([labelled])
528 });
529 Parts {
530 incoming: self.incoming.map(|(code, text)| (&line[..code.end], text)),
531 inside: inside.chain(unlabelled(last, end)).collect(),
532 outgoing: self.outgoing.map(|(code, sty)| (&line[code.start..], sty)),
533 }
534 }
535}
536
537impl<C, T> Parts<C, T> {
538 #[must_use]
539 fn map_code<C1>(self, mut f: impl FnMut(C) -> C1) -> Parts<C1, T> {
540 let inside = self.inside.into_iter();
541 Parts {
542 incoming: self.incoming.map(|(code, text)| (f(code), text)),
543 inside: inside.map(|(code, label)| (f(code), label)).collect(),
544 outgoing: self.outgoing.map(|(code, style)| (f(code), style)),
545 }
546 }
547}
548
549fn width<'a, C: 'a, T: 'a>(i: impl IntoIterator<Item = &'a (CodeWidth<C>, T)>) -> usize {
550 i.into_iter().map(|(code, _)| code.width).sum()
551}
552
553impl<C, T> Parts<CodeWidth<C>, T> {
554 fn width(&self) -> usize {
556 width(&self.inside) + width(&self.incoming) + width(&self.outgoing)
557 }
558
559 fn fmt_incoming(&self, style: &Style, snake: Snake, f: &mut Formatter) -> fmt::Result {
560 if let Some((code, _text)) = &self.incoming {
561 write!(f, "{}{}", " ".repeat(code.width), style(snake.to_string()))?;
562 }
563 Ok(())
564 }
565
566 fn fmt_inside<S1, S2>(&self, from: usize, s1: S1, s2: S2, f: &mut Formatter) -> fmt::Result
567 where
568 S1: Fn(usize) -> String,
570 S2: Fn(usize, usize) -> String,
572 {
573 let before = width(&self.inside[..from]);
574 write!(f, "{}", " ".repeat(before))?;
575
576 for (code, label) in &self.inside[from..] {
577 match label {
578 Some((Some(_text), style)) => {
579 let (left, right) = code.left_right();
580 style(s2(left, right)).fmt(f)?
581 }
582 Some((None, style)) => style(s1(code.width)).fmt(f)?,
583 None => " ".repeat(code.width).fmt(f)?,
584 }
585 }
586 Ok(())
587 }
588
589 fn fmt_arrows(&self, f: &mut Formatter) -> fmt::Result {
591 self.fmt_inside(0, Snake::line, Snake::line_down_line, f)?;
592
593 if let Some((_code, style)) = &self.outgoing {
594 style(Snake::ArrowUp.to_string()).fmt(f)?;
595 }
596 Ok(())
597 }
598
599 fn fmt_inside_vert(&self, from: usize, f: &mut Formatter) -> fmt::Result {
601 let s1 = |w| " ".repeat(w).to_string();
602 let s2 = |l, r| format!("{}{}{}", " ".repeat(l), Snake::Vertical, " ".repeat(r));
603 self.fmt_inside(from, s1, s2, f)?;
604
605 if let Some((_code, style)) = &self.outgoing {
606 write!(f, "{}", style(Snake::Vertical.to_string()))?;
607 }
608 Ok(())
609 }
610}
611
612#[derive(Copy, Clone)]
614enum Snake {
615 Horizontal,
617 Vertical,
619 VerticalDots,
621 UpRight,
623 RightUp,
625 DownRight,
627 ArrowUp,
629 HorizontalUp,
631 HorizontalDown,
633}
634
635impl Snake {
636 fn line(len: usize) -> String {
638 "─".repeat(len)
639 }
640
641 fn down_line(len: usize) -> String {
643 format!("{}{}", Snake::DownRight, Snake::line(len))
644 }
645
646 fn down_line_up_line(l: usize, r: usize) -> String {
648 format!(
649 "{}{}{}{}",
650 Self::DownRight,
651 Self::line(l),
652 Self::HorizontalUp,
653 Self::line(r)
654 )
655 }
656
657 fn down_line_up(len: usize) -> String {
659 format!("{}{}{}", Self::DownRight, Self::line(len), Self::RightUp)
660 }
661
662 fn up_line_up(len: usize) -> String {
664 format!("{}{}{}", Self::UpRight, Self::line(len), Self::RightUp)
665 }
666
667 fn line_up(len: usize) -> String {
669 format!("{}{}", Self::line(len), Self::RightUp)
670 }
671
672 fn line_down_line(l: usize, r: usize) -> String {
674 format!("{}{}{}", Self::line(l), Self::HorizontalDown, Self::line(r))
675 }
676}
677
678impl Display for Snake {
679 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
680 match self {
681 Self::Horizontal => "─",
682 Self::Vertical => "│",
683 Self::VerticalDots => "┆",
684 Self::UpRight => "╭",
685 Self::RightUp => "╯",
686 Self::DownRight => "╰",
687 Self::ArrowUp => "▲",
688 Self::HorizontalUp => "┴",
689 Self::HorizontalDown => "┬",
690 }
691 .fmt(f)
692 }
693}