1use std::{borrow::Cow, fmt};
2
3use unicode_segmentation::UnicodeSegmentation;
4use unicode_width::UnicodeWidthStr;
5
6use crate::{
7 buffer::Buffer,
8 layout::Rect,
9 style::{Style, Styled},
10 text::{Line, StyledGrapheme},
11 widgets::{Widget, WidgetRef},
12};
13
14#[derive(Default, Clone, Eq, PartialEq, Hash)]
101pub struct Span<'a> {
102 pub style: Style,
104 pub content: Cow<'a, str>,
106}
107
108impl fmt::Debug for Span<'_> {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 if self.content.is_empty() {
111 write!(f, "Span::default()")?;
112 } else {
113 write!(f, "Span::from({:?})", self.content)?;
114 }
115 if self.style != Style::default() {
116 self.style.fmt_stylize(f)?;
117 }
118 Ok(())
119 }
120}
121
122impl<'a> Span<'a> {
123 pub fn raw<T>(content: T) -> Self
134 where
135 T: Into<Cow<'a, str>>,
136 {
137 Self {
138 content: content.into(),
139 style: Style::default(),
140 }
141 }
142
143 pub fn styled<T, S>(content: T, style: S) -> Self
166 where
167 T: Into<Cow<'a, str>>,
168 S: Into<Style>,
169 {
170 Self {
171 content: content.into(),
172 style: style.into(),
173 }
174 }
175
176 #[must_use = "method moves the value of self and returns the modified value"]
191 pub fn content<T>(mut self, content: T) -> Self
192 where
193 T: Into<Cow<'a, str>>,
194 {
195 self.content = content.into();
196 self
197 }
198
199 #[must_use = "method moves the value of self and returns the modified value"]
222 pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
223 self.style = style.into();
224 self
225 }
226
227 #[must_use = "method moves the value of self and returns the modified value"]
249 pub fn patch_style<S: Into<Style>>(mut self, style: S) -> Self {
250 self.style = self.style.patch(style);
251 self
252 }
253
254 #[must_use = "method moves the value of self and returns the modified value"]
276 pub fn reset_style(self) -> Self {
277 self.patch_style(Style::reset())
278 }
279
280 pub fn width(&self) -> usize {
282 self.content.width()
283 }
284
285 pub fn styled_graphemes<S: Into<Style>>(
319 &'a self,
320 base_style: S,
321 ) -> impl Iterator<Item = StyledGrapheme<'a>> {
322 let style = base_style.into().patch(self.style);
323 self.content
324 .as_ref()
325 .graphemes(true)
326 .filter(|g| *g != "\n")
327 .map(move |g| StyledGrapheme { symbol: g, style })
328 }
329
330 #[must_use = "method moves the value of self and returns the modified value"]
340 pub fn into_left_aligned_line(self) -> Line<'a> {
341 Line::from(self).left_aligned()
342 }
343
344 #[allow(clippy::wrong_self_convention)]
345 #[deprecated = "use into_left_aligned_line"]
346 pub fn to_left_aligned_line(self) -> Line<'a> {
347 self.into_left_aligned_line()
348 }
349
350 #[must_use = "method moves the value of self and returns the modified value"]
360 pub fn into_centered_line(self) -> Line<'a> {
361 Line::from(self).centered()
362 }
363
364 #[allow(clippy::wrong_self_convention)]
365 #[deprecated = "use into_centered_line"]
366 pub fn to_centered_line(self) -> Line<'a> {
367 self.into_centered_line()
368 }
369
370 #[must_use = "method moves the value of self and returns the modified value"]
380 pub fn into_right_aligned_line(self) -> Line<'a> {
381 Line::from(self).right_aligned()
382 }
383
384 #[allow(clippy::wrong_self_convention)]
385 #[deprecated = "use into_right_aligned_line"]
386 pub fn to_right_aligned_line(self) -> Line<'a> {
387 self.into_right_aligned_line()
388 }
389}
390
391impl<'a, T> From<T> for Span<'a>
392where
393 T: Into<Cow<'a, str>>,
394{
395 fn from(s: T) -> Self {
396 Span::raw(s.into())
397 }
398}
399
400impl<'a> std::ops::Add<Self> for Span<'a> {
401 type Output = Line<'a>;
402
403 fn add(self, rhs: Self) -> Self::Output {
404 Line::from_iter([self, rhs])
405 }
406}
407
408impl<'a> Styled for Span<'a> {
409 type Item = Self;
410
411 fn style(&self) -> Style {
412 self.style
413 }
414
415 fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
416 self.style(style)
417 }
418}
419
420impl Widget for Span<'_> {
421 fn render(self, area: Rect, buf: &mut Buffer) {
422 self.render_ref(area, buf);
423 }
424}
425
426impl WidgetRef for Span<'_> {
427 fn render_ref(&self, area: Rect, buf: &mut Buffer) {
428 let area = area.intersection(buf.area);
429 if area.is_empty() {
430 return;
431 }
432 let Rect { mut x, y, .. } = area;
433 for (i, grapheme) in self.styled_graphemes(Style::default()).enumerate() {
434 let symbol_width = grapheme.symbol.width();
435 let next_x = x.saturating_add(symbol_width as u16);
436 if next_x > area.right() {
437 break;
438 }
439
440 if i == 0 {
441 buf[(x, y)]
443 .set_symbol(grapheme.symbol)
444 .set_style(grapheme.style);
445 } else if x == area.x {
446 buf[(x, y)]
449 .append_symbol(grapheme.symbol)
450 .set_style(grapheme.style);
451 } else if symbol_width == 0 {
452 buf[(x - 1, y)]
454 .append_symbol(grapheme.symbol)
455 .set_style(grapheme.style);
456 } else {
457 buf[(x, y)]
459 .set_symbol(grapheme.symbol)
460 .set_style(grapheme.style);
461 }
462
463 for x_hidden in (x + 1)..next_x {
467 buf[(x_hidden, y)].reset();
470 }
471 x = next_x;
472 }
473 }
474}
475
476pub trait ToSpan {
484 fn to_span(&self) -> Span<'_>;
486}
487
488impl<T: fmt::Display> ToSpan for T {
494 fn to_span(&self) -> Span<'_> {
495 Span::raw(self.to_string())
496 }
497}
498
499impl fmt::Display for Span<'_> {
500 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
501 for line in self.content.lines() {
502 fmt::Display::fmt(line, f)?;
503 }
504 Ok(())
505 }
506}
507
508#[cfg(test)]
509mod tests {
510 use rstest::{fixture, rstest};
511
512 use super::*;
513 use crate::{buffer::Cell, layout::Alignment, style::Stylize};
514
515 #[fixture]
516 fn small_buf() -> Buffer {
517 Buffer::empty(Rect::new(0, 0, 10, 1))
518 }
519
520 #[test]
521 fn default() {
522 let span = Span::default();
523 assert_eq!(span.content, Cow::Borrowed(""));
524 assert_eq!(span.style, Style::default());
525 }
526
527 #[test]
528 fn raw_str() {
529 let span = Span::raw("test content");
530 assert_eq!(span.content, Cow::Borrowed("test content"));
531 assert_eq!(span.style, Style::default());
532 }
533
534 #[test]
535 fn raw_string() {
536 let content = String::from("test content");
537 let span = Span::raw(content.clone());
538 assert_eq!(span.content, Cow::Owned::<str>(content));
539 assert_eq!(span.style, Style::default());
540 }
541
542 #[test]
543 fn styled_str() {
544 let style = Style::new().red();
545 let span = Span::styled("test content", style);
546 assert_eq!(span.content, Cow::Borrowed("test content"));
547 assert_eq!(span.style, Style::new().red());
548 }
549
550 #[test]
551 fn styled_string() {
552 let content = String::from("test content");
553 let style = Style::new().green();
554 let span = Span::styled(content.clone(), style);
555 assert_eq!(span.content, Cow::Owned::<str>(content));
556 assert_eq!(span.style, style);
557 }
558
559 #[test]
560 fn set_content() {
561 let span = Span::default().content("test content");
562 assert_eq!(span.content, Cow::Borrowed("test content"));
563 }
564
565 #[test]
566 fn set_style() {
567 let span = Span::default().style(Style::new().green());
568 assert_eq!(span.style, Style::new().green());
569 }
570
571 #[test]
572 fn from_ref_str_borrowed_cow() {
573 let content = "test content";
574 let span = Span::from(content);
575 assert_eq!(span.content, Cow::Borrowed(content));
576 assert_eq!(span.style, Style::default());
577 }
578
579 #[test]
580 fn from_string_ref_str_borrowed_cow() {
581 let content = String::from("test content");
582 let span = Span::from(content.as_str());
583 assert_eq!(span.content, Cow::Borrowed(content.as_str()));
584 assert_eq!(span.style, Style::default());
585 }
586
587 #[test]
588 fn from_string_owned_cow() {
589 let content = String::from("test content");
590 let span = Span::from(content.clone());
591 assert_eq!(span.content, Cow::Owned::<str>(content));
592 assert_eq!(span.style, Style::default());
593 }
594
595 #[test]
596 fn from_ref_string_borrowed_cow() {
597 let content = String::from("test content");
598 let span = Span::from(&content);
599 assert_eq!(span.content, Cow::Borrowed(content.as_str()));
600 assert_eq!(span.style, Style::default());
601 }
602
603 #[test]
604 fn to_span() {
605 assert_eq!(42.to_span(), Span::raw("42"));
606 assert_eq!("test".to_span(), Span::raw("test"));
607 }
608
609 #[test]
610 fn reset_style() {
611 let span = Span::styled("test content", Style::new().green()).reset_style();
612 assert_eq!(span.style, Style::reset());
613 }
614
615 #[test]
616 fn patch_style() {
617 let span = Span::styled("test content", Style::new().green().on_yellow())
618 .patch_style(Style::new().red().bold());
619 assert_eq!(span.style, Style::new().red().on_yellow().bold());
620 }
621
622 #[test]
623 fn width() {
624 assert_eq!(Span::raw("").width(), 0);
625 assert_eq!(Span::raw("test").width(), 4);
626 assert_eq!(Span::raw("test content").width(), 12);
627 assert_eq!(Span::raw("test\ncontent").width(), 12);
629 }
630
631 #[test]
632 fn stylize() {
633 let span = Span::raw("test content").green();
634 assert_eq!(span.content, Cow::Borrowed("test content"));
635 assert_eq!(span.style, Style::new().green());
636
637 let span = Span::styled("test content", Style::new().green());
638 let stylized = span.on_yellow().bold();
639 assert_eq!(stylized.content, Cow::Borrowed("test content"));
640 assert_eq!(stylized.style, Style::new().green().on_yellow().bold());
641 }
642
643 #[test]
644 fn display_span() {
645 let span = Span::raw("test content");
646 assert_eq!(format!("{span}"), "test content");
647 assert_eq!(format!("{span:.4}"), "test");
648 }
649
650 #[test]
651 fn display_newline_span() {
652 let span = Span::raw("test\ncontent");
653 assert_eq!(format!("{span}"), "testcontent");
654 }
655
656 #[test]
657 fn display_styled_span() {
658 let stylized_span = Span::styled("stylized test content", Style::new().green());
659 assert_eq!(format!("{stylized_span}"), "stylized test content");
660 assert_eq!(format!("{stylized_span:.8}"), "stylized");
661 }
662
663 #[test]
664 fn left_aligned() {
665 let span = Span::styled("Test Content", Style::new().green().italic());
666 let line = span.into_left_aligned_line();
667 assert_eq!(line.alignment, Some(Alignment::Left));
668 }
669
670 #[test]
671 fn centered() {
672 let span = Span::styled("Test Content", Style::new().green().italic());
673 let line = span.into_centered_line();
674 assert_eq!(line.alignment, Some(Alignment::Center));
675 }
676
677 #[test]
678 fn right_aligned() {
679 let span = Span::styled("Test Content", Style::new().green().italic());
680 let line = span.into_right_aligned_line();
681 assert_eq!(line.alignment, Some(Alignment::Right));
682 }
683
684 mod widget {
685 use rstest::rstest;
686
687 use super::*;
688
689 #[test]
690 fn render() {
691 let style = Style::new().green().on_yellow();
692 let span = Span::styled("test content", style);
693 let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
694 span.render(buf.area, &mut buf);
695 let expected = Buffer::with_lines([Line::from(vec![
696 "test content".green().on_yellow(),
697 " ".into(),
698 ])]);
699 assert_eq!(buf, expected);
700 }
701
702 #[rstest]
703 #[case::x(20, 0)]
704 #[case::y(0, 20)]
705 #[case::both(20, 20)]
706 fn render_out_of_bounds(mut small_buf: Buffer, #[case] x: u16, #[case] y: u16) {
707 let out_of_bounds = Rect::new(x, y, 10, 1);
708 Span::raw("Hello, World!").render(out_of_bounds, &mut small_buf);
709 assert_eq!(small_buf, Buffer::empty(small_buf.area));
710 }
711
712 #[test]
715 fn render_truncates_too_long_content() {
716 let style = Style::new().green().on_yellow();
717 let span = Span::styled("test content", style);
718
719 let mut buf = Buffer::empty(Rect::new(0, 0, 10, 1));
720 span.render(Rect::new(0, 0, 5, 1), &mut buf);
721
722 let expected = Buffer::with_lines([Line::from(vec![
723 "test ".green().on_yellow(),
724 " ".into(),
725 ])]);
726 assert_eq!(buf, expected);
727 }
728
729 #[test]
732 fn render_patches_existing_style() {
733 let style = Style::new().green().on_yellow();
734 let span = Span::styled("test content", style);
735 let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
736 buf.set_style(buf.area, Style::new().italic());
737 span.render(buf.area, &mut buf);
738 let expected = Buffer::with_lines([Line::from(vec![
739 "test content".green().on_yellow().italic(),
740 " ".italic(),
741 ])]);
742 assert_eq!(buf, expected);
743 }
744
745 #[test]
748 fn render_multi_width_symbol() {
749 let style = Style::new().green().on_yellow();
750 let span = Span::styled("test 😃 content", style);
751 let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
752 span.render(buf.area, &mut buf);
753 let expected = Buffer::with_lines(["test 😃 content".green().on_yellow()]);
757 assert_eq!(buf, expected);
758 }
759
760 #[test]
763 fn render_multi_width_symbol_truncates_entire_symbol() {
764 let style = Style::new().green().on_yellow();
766 let span = Span::styled("test 😃 content", style);
767 let mut buf = Buffer::empty(Rect::new(0, 0, 6, 1));
768 span.render(buf.area, &mut buf);
769
770 let expected =
771 Buffer::with_lines([Line::from(vec!["test ".green().on_yellow(), " ".into()])]);
772 assert_eq!(buf, expected);
773 }
774
775 #[test]
778 fn render_overflowing_area_truncates() {
779 let style = Style::new().green().on_yellow();
780 let span = Span::styled("test content", style);
781 let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
782 span.render(Rect::new(10, 0, 20, 1), &mut buf);
783
784 let expected = Buffer::with_lines([Line::from(vec![
785 " ".into(),
786 "test ".green().on_yellow(),
787 ])]);
788 assert_eq!(buf, expected);
789 }
790
791 #[test]
792 fn render_first_zero_width() {
793 let span = Span::raw("\u{200B}abc");
794 let mut buf = Buffer::empty(Rect::new(0, 0, 3, 1));
795 span.render(buf.area, &mut buf);
796 assert_eq!(
797 buf.content(),
798 [Cell::new("\u{200B}a"), Cell::new("b"), Cell::new("c"),]
799 );
800 }
801
802 #[test]
803 fn render_second_zero_width() {
804 let span = Span::raw("a\u{200B}bc");
805 let mut buf = Buffer::empty(Rect::new(0, 0, 3, 1));
806 span.render(buf.area, &mut buf);
807 assert_eq!(
808 buf.content(),
809 [Cell::new("a\u{200B}"), Cell::new("b"), Cell::new("c")]
810 );
811 }
812
813 #[test]
814 fn render_middle_zero_width() {
815 let span = Span::raw("ab\u{200B}c");
816 let mut buf = Buffer::empty(Rect::new(0, 0, 3, 1));
817 span.render(buf.area, &mut buf);
818 assert_eq!(
819 buf.content(),
820 [Cell::new("a"), Cell::new("b\u{200B}"), Cell::new("c")]
821 );
822 }
823
824 #[test]
825 fn render_last_zero_width() {
826 let span = Span::raw("abc\u{200B}");
827 let mut buf = Buffer::empty(Rect::new(0, 0, 3, 1));
828 span.render(buf.area, &mut buf);
829 assert_eq!(
830 buf.content(),
831 [Cell::new("a"), Cell::new("b"), Cell::new("c\u{200B}")]
832 );
833 }
834
835 #[test]
836 fn render_with_newlines() {
837 let span = Span::raw("a\nb");
838 let mut buf = Buffer::empty(Rect::new(0, 0, 2, 1));
839 span.render(buf.area, &mut buf);
840 assert_eq!(buf.content(), [Cell::new("a"), Cell::new("b")]);
841 }
842 }
843
844 #[test]
851 fn issue_1160() {
852 let span = Span::raw("Hello\u{200E}");
853 let mut buf = Buffer::empty(Rect::new(0, 0, 5, 1));
854 span.render(buf.area, &mut buf);
855 assert_eq!(
856 buf.content(),
857 [
858 Cell::new("H"),
859 Cell::new("e"),
860 Cell::new("l"),
861 Cell::new("l"),
862 Cell::new("o\u{200E}"),
863 ]
864 );
865 }
866
867 #[test]
868 fn add() {
869 assert_eq!(
870 Span::default() + Span::default(),
871 Line::from(vec![Span::default(), Span::default()])
872 );
873
874 assert_eq!(
875 Span::default() + Span::raw("test"),
876 Line::from(vec![Span::default(), Span::raw("test")])
877 );
878
879 assert_eq!(
880 Span::raw("test") + Span::default(),
881 Line::from(vec![Span::raw("test"), Span::default()])
882 );
883
884 assert_eq!(
885 Span::raw("test") + Span::raw("content"),
886 Line::from(vec![Span::raw("test"), Span::raw("content")])
887 );
888 }
889
890 #[rstest]
891 #[case::default(Span::default(), "Span::default()")]
892 #[case::raw(Span::raw("test"), r#"Span::from("test")"#)]
893 #[case::styled(Span::styled("test", Style::new().green()), r#"Span::from("test").green()"#)]
894 #[case::styled_italic(
895 Span::styled("test", Style::new().green().italic()),
896 r#"Span::from("test").green().italic()"#
897 )]
898 fn debug(#[case] span: Span, #[case] expected: &str) {
899 assert_eq!(format!("{span:?}"), expected);
900 }
901}