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