1use unicode_width::UnicodeWidthStr;
2
3use crate::{
4 buffer::Buffer,
5 layout::{Alignment, Position, Rect},
6 style::{Style, Styled},
7 text::{Line, StyledGrapheme, Text},
8 widgets::{
9 block::BlockExt,
10 reflow::{LineComposer, LineTruncator, WordWrapper, WrappedLine},
11 Block, Widget, WidgetRef,
12 },
13};
14
15const fn get_line_offset(line_width: u16, text_area_width: u16, alignment: Alignment) -> u16 {
16 match alignment {
17 Alignment::Center => (text_area_width / 2).saturating_sub(line_width / 2),
18 Alignment::Right => text_area_width.saturating_sub(line_width),
19 Alignment::Left => 0,
20 }
21}
22
23#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
89pub struct Paragraph<'a> {
90 block: Option<Block<'a>>,
92 style: Style,
94 wrap: Option<Wrap>,
96 text: Text<'a>,
98 scroll: Position,
100 alignment: Alignment,
102}
103
104#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
137pub struct Wrap {
138 pub trim: bool,
140}
141
142type Horizontal = u16;
143type Vertical = u16;
144
145impl<'a> Paragraph<'a> {
146 pub fn new<T>(text: T) -> Self
167 where
168 T: Into<Text<'a>>,
169 {
170 Self {
171 block: None,
172 style: Style::default(),
173 wrap: None,
174 text: text.into(),
175 scroll: Position::ORIGIN,
176 alignment: Alignment::Left,
177 }
178 }
179
180 #[must_use = "method moves the value of self and returns the modified value"]
190 pub fn block(mut self, block: Block<'a>) -> Self {
191 self.block = Some(block);
192 self
193 }
194
195 #[must_use = "method moves the value of self and returns the modified value"]
216 pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
217 self.style = style.into();
218 self
219 }
220
221 #[must_use = "method moves the value of self and returns the modified value"]
233 pub const fn wrap(mut self, wrap: Wrap) -> Self {
234 self.wrap = Some(wrap);
235 self
236 }
237
238 #[must_use = "method moves the value of self and returns the modified value"]
250 pub const fn scroll(mut self, offset: (Vertical, Horizontal)) -> Self {
251 self.scroll = Position {
252 x: offset.1,
253 y: offset.0,
254 };
255 self
256 }
257
258 #[must_use = "method moves the value of self and returns the modified value"]
271 pub const fn alignment(mut self, alignment: Alignment) -> Self {
272 self.alignment = alignment;
273 self
274 }
275
276 #[must_use = "method moves the value of self and returns the modified value"]
288 pub const fn left_aligned(self) -> Self {
289 self.alignment(Alignment::Left)
290 }
291
292 #[must_use = "method moves the value of self and returns the modified value"]
304 pub const fn centered(self) -> Self {
305 self.alignment(Alignment::Center)
306 }
307
308 #[must_use = "method moves the value of self and returns the modified value"]
320 pub const fn right_aligned(self) -> Self {
321 self.alignment(Alignment::Right)
322 }
323
324 #[instability::unstable(
345 feature = "rendered-line-info",
346 issue = "https://github.com/ratatui/ratatui/issues/293"
347 )]
348 pub fn line_count(&self, width: u16) -> usize {
349 if width < 1 {
350 return 0;
351 }
352
353 let (top, bottom) = self
354 .block
355 .as_ref()
356 .map(Block::vertical_space)
357 .unwrap_or_default();
358
359 let count = if let Some(Wrap { trim }) = self.wrap {
360 let styled = self.text.iter().map(|line| {
361 let graphemes = line
362 .spans
363 .iter()
364 .flat_map(|span| span.styled_graphemes(self.style));
365 let alignment = line.alignment.unwrap_or(self.alignment);
366 (graphemes, alignment)
367 });
368 let mut line_composer = WordWrapper::new(styled, width, trim);
369 let mut count = 0;
370 while line_composer.next_line().is_some() {
371 count += 1;
372 }
373 count
374 } else {
375 self.text.height()
376 };
377
378 count
379 .saturating_add(top as usize)
380 .saturating_add(bottom as usize)
381 }
382
383 #[instability::unstable(
401 feature = "rendered-line-info",
402 issue = "https://github.com/ratatui/ratatui/issues/293"
403 )]
404 pub fn line_width(&self) -> usize {
405 let width = self.text.iter().map(Line::width).max().unwrap_or_default();
406 let (left, right) = self
407 .block
408 .as_ref()
409 .map(Block::horizontal_space)
410 .unwrap_or_default();
411
412 width
413 .saturating_add(left as usize)
414 .saturating_add(right as usize)
415 }
416}
417
418impl Widget for Paragraph<'_> {
419 fn render(self, area: Rect, buf: &mut Buffer) {
420 self.render_ref(area, buf);
421 }
422}
423
424impl WidgetRef for Paragraph<'_> {
425 fn render_ref(&self, area: Rect, buf: &mut Buffer) {
426 buf.set_style(area, self.style);
427 self.block.render_ref(area, buf);
428 let inner = self.block.inner_if_some(area);
429 self.render_paragraph(inner, buf);
430 }
431}
432
433impl Paragraph<'_> {
434 fn render_paragraph(&self, text_area: Rect, buf: &mut Buffer) {
435 if text_area.is_empty() {
436 return;
437 }
438
439 buf.set_style(text_area, self.style);
440 let styled = self.text.iter().map(|line| {
441 let graphemes = line.styled_graphemes(self.text.style);
442 let alignment = line.alignment.unwrap_or(self.alignment);
443 (graphemes, alignment)
444 });
445
446 if let Some(Wrap { trim }) = self.wrap {
447 let line_composer = WordWrapper::new(styled, text_area.width, trim);
448 self.render_text(line_composer, text_area, buf);
449 } else {
450 let mut line_composer = LineTruncator::new(styled, text_area.width);
451 line_composer.set_horizontal_offset(self.scroll.x);
452 self.render_text(line_composer, text_area, buf);
453 }
454 }
455}
456
457impl<'a> Paragraph<'a> {
458 fn render_text<C: LineComposer<'a>>(&self, mut composer: C, area: Rect, buf: &mut Buffer) {
459 let mut y = 0;
460 while let Some(WrappedLine {
461 line: current_line,
462 width: current_line_width,
463 alignment: current_line_alignment,
464 }) = composer.next_line()
465 {
466 if y >= self.scroll.y {
467 let mut x = get_line_offset(current_line_width, area.width, current_line_alignment);
468 for StyledGrapheme { symbol, style } in current_line {
469 let width = symbol.width();
470 if width == 0 {
471 continue;
472 }
473 let symbol = if symbol.is_empty() { " " } else { symbol };
476 buf[(area.left() + x, area.top() + y - self.scroll.y)]
477 .set_symbol(symbol)
478 .set_style(*style);
479 x += width as u16;
480 }
481 }
482 y += 1;
483 if y >= area.height + self.scroll.y {
484 break;
485 }
486 }
487 }
488}
489
490impl<'a> Styled for Paragraph<'a> {
491 type Item = Self;
492
493 fn style(&self) -> Style {
494 self.style
495 }
496
497 fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
498 self.style(style)
499 }
500}
501
502#[cfg(test)]
503mod test {
504 use super::*;
505 use crate::{
506 backend::TestBackend,
507 buffer::Buffer,
508 layout::{Alignment, Rect},
509 style::{Color, Modifier, Style, Stylize},
510 text::{Line, Span, Text},
511 widgets::{block::Position, Borders, Widget},
512 Terminal,
513 };
514
515 #[track_caller]
520 fn test_case(paragraph: &Paragraph, expected: &Buffer) {
521 let backend = TestBackend::new(expected.area.width, expected.area.height);
522 let mut terminal = Terminal::new(backend).unwrap();
523 terminal
524 .draw(|f| f.render_widget(paragraph.clone(), f.area()))
525 .unwrap();
526 terminal.backend().assert_buffer(expected);
527 }
528
529 #[test]
530 fn zero_width_char_at_end_of_line() {
531 let line = "foo\u{200B}";
532 for paragraph in [
533 Paragraph::new(line),
534 Paragraph::new(line).wrap(Wrap { trim: false }),
535 Paragraph::new(line).wrap(Wrap { trim: true }),
536 ] {
537 test_case(¶graph, &Buffer::with_lines(["foo"]));
538 test_case(¶graph, &Buffer::with_lines(["foo "]));
539 test_case(¶graph, &Buffer::with_lines(["foo ", " "]));
540 test_case(¶graph, &Buffer::with_lines(["foo", " "]));
541 }
542 }
543
544 #[test]
545 fn test_render_empty_paragraph() {
546 for paragraph in [
547 Paragraph::new(""),
548 Paragraph::new("").wrap(Wrap { trim: false }),
549 Paragraph::new("").wrap(Wrap { trim: true }),
550 ] {
551 test_case(¶graph, &Buffer::with_lines([" "]));
552 test_case(¶graph, &Buffer::with_lines([" "]));
553 test_case(¶graph, &Buffer::with_lines([" "; 10]));
554 test_case(¶graph, &Buffer::with_lines([" ", " "]));
555 }
556 }
557
558 #[test]
559 fn test_render_single_line_paragraph() {
560 let text = "Hello, world!";
561 for paragraph in [
562 Paragraph::new(text),
563 Paragraph::new(text).wrap(Wrap { trim: false }),
564 Paragraph::new(text).wrap(Wrap { trim: true }),
565 ] {
566 test_case(¶graph, &Buffer::with_lines(["Hello, world! "]));
567 test_case(¶graph, &Buffer::with_lines(["Hello, world!"]));
568 test_case(
569 ¶graph,
570 &Buffer::with_lines(["Hello, world! ", " "]),
571 );
572 test_case(
573 ¶graph,
574 &Buffer::with_lines(["Hello, world!", " "]),
575 );
576 }
577 }
578
579 #[test]
580 fn test_render_multi_line_paragraph() {
581 let text = "This is a\nmultiline\nparagraph.";
582 for paragraph in [
583 Paragraph::new(text),
584 Paragraph::new(text).wrap(Wrap { trim: false }),
585 Paragraph::new(text).wrap(Wrap { trim: true }),
586 ] {
587 test_case(
588 ¶graph,
589 &Buffer::with_lines(["This is a ", "multiline ", "paragraph."]),
590 );
591 test_case(
592 ¶graph,
593 &Buffer::with_lines(["This is a ", "multiline ", "paragraph. "]),
594 );
595 test_case(
596 ¶graph,
597 &Buffer::with_lines([
598 "This is a ",
599 "multiline ",
600 "paragraph. ",
601 " ",
602 " ",
603 ]),
604 );
605 }
606 }
607
608 #[test]
609 fn test_render_paragraph_with_block() {
610 let text = "Hello, worlds!";
613 let truncated_paragraph = Paragraph::new(text).block(Block::bordered().title("Title"));
614 let wrapped_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: false });
615 let trimmed_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: true });
616
617 for paragraph in [&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph] {
618 #[rustfmt::skip]
619 test_case(
620 paragraph,
621 &Buffer::with_lines([
622 "┌Title─────────┐",
623 "│Hello, worlds!│",
624 "└──────────────┘",
625 ]),
626 );
627 test_case(
628 paragraph,
629 &Buffer::with_lines([
630 "┌Title───────────┐",
631 "│Hello, worlds! │",
632 "└────────────────┘",
633 ]),
634 );
635 test_case(
636 paragraph,
637 &Buffer::with_lines([
638 "┌Title────────────┐",
639 "│Hello, worlds! │",
640 "│ │",
641 "└─────────────────┘",
642 ]),
643 );
644 }
645
646 test_case(
647 &truncated_paragraph,
648 &Buffer::with_lines([
649 "┌Title───────┐",
650 "│Hello, world│",
651 "│ │",
652 "└────────────┘",
653 ]),
654 );
655 test_case(
656 &wrapped_paragraph,
657 &Buffer::with_lines([
658 "┌Title──────┐",
659 "│Hello, │",
660 "│worlds! │",
661 "└───────────┘",
662 ]),
663 );
664 test_case(
665 &trimmed_paragraph,
666 &Buffer::with_lines([
667 "┌Title──────┐",
668 "│Hello, │",
669 "│worlds! │",
670 "└───────────┘",
671 ]),
672 );
673 }
674
675 #[test]
676 fn test_render_line_styled() {
677 let l0 = Line::raw("unformatted");
678 let l1 = Line::styled("bold text", Style::new().bold());
679 let l2 = Line::styled("cyan text", Style::new().cyan());
680 let l3 = Line::styled("dim text", Style::new().dim());
681 let paragraph = Paragraph::new(vec![l0, l1, l2, l3]);
682
683 let mut expected =
684 Buffer::with_lines(["unformatted", "bold text", "cyan text", "dim text"]);
685 expected.set_style(Rect::new(0, 1, 9, 1), Style::new().bold());
686 expected.set_style(Rect::new(0, 2, 9, 1), Style::new().cyan());
687 expected.set_style(Rect::new(0, 3, 8, 1), Style::new().dim());
688
689 test_case(¶graph, &expected);
690 }
691
692 #[test]
693 fn test_render_line_spans_styled() {
694 let l0 = Line::default().spans([
695 Span::styled("bold", Style::new().bold()),
696 Span::raw(" and "),
697 Span::styled("cyan", Style::new().cyan()),
698 ]);
699 let l1 = Line::default().spans([Span::raw("unformatted")]);
700 let paragraph = Paragraph::new(vec![l0, l1]);
701
702 let mut expected = Buffer::with_lines(["bold and cyan", "unformatted"]);
703 expected.set_style(Rect::new(0, 0, 4, 1), Style::new().bold());
704 expected.set_style(Rect::new(9, 0, 4, 1), Style::new().cyan());
705
706 test_case(¶graph, &expected);
707 }
708
709 #[test]
710 fn test_render_paragraph_with_block_with_bottom_title_and_border() {
711 let block = Block::new()
712 .borders(Borders::BOTTOM)
713 .title_position(Position::Bottom)
714 .title("Title");
715 let paragraph = Paragraph::new("Hello, world!").block(block);
716 test_case(
717 ¶graph,
718 &Buffer::with_lines(["Hello, world! ", "Title──────────"]),
719 );
720 }
721
722 #[test]
723 fn test_render_paragraph_with_word_wrap() {
724 let text = "This is a long line of text that should wrap and contains a superultramegagigalong word.";
725 let wrapped_paragraph = Paragraph::new(text).wrap(Wrap { trim: false });
726 let trimmed_paragraph = Paragraph::new(text).wrap(Wrap { trim: true });
727
728 test_case(
729 &wrapped_paragraph,
730 &Buffer::with_lines([
731 "This is a long line",
732 "of text that should",
733 "wrap and ",
734 "contains a ",
735 "superultramegagigal",
736 "ong word. ",
737 ]),
738 );
739 test_case(
740 &wrapped_paragraph,
741 &Buffer::with_lines([
742 "This is a ",
743 "long line of",
744 "text that ",
745 "should wrap ",
746 " and ",
747 "contains a ",
748 "superultrame",
749 "gagigalong ",
750 "word. ",
751 ]),
752 );
753
754 test_case(
755 &trimmed_paragraph,
756 &Buffer::with_lines([
757 "This is a long line",
758 "of text that should",
759 "wrap and ",
760 "contains a ",
761 "superultramegagigal",
762 "ong word. ",
763 ]),
764 );
765 test_case(
766 &trimmed_paragraph,
767 &Buffer::with_lines([
768 "This is a ",
769 "long line of",
770 "text that ",
771 "should wrap ",
772 "and contains",
773 "a ",
774 "superultrame",
775 "gagigalong ",
776 "word. ",
777 ]),
778 );
779 }
780
781 #[test]
782 fn test_render_paragraph_with_line_truncation() {
783 let text = "This is a long line of text that should be truncated.";
784 let truncated_paragraph = Paragraph::new(text);
785
786 test_case(
787 &truncated_paragraph,
788 &Buffer::with_lines(["This is a long line of"]),
789 );
790 test_case(
791 &truncated_paragraph,
792 &Buffer::with_lines(["This is a long line of te"]),
793 );
794 test_case(
795 &truncated_paragraph,
796 &Buffer::with_lines(["This is a long line of "]),
797 );
798 test_case(
799 &truncated_paragraph.clone().scroll((0, 2)),
800 &Buffer::with_lines(["is is a long line of te"]),
801 );
802 }
803
804 #[test]
805 fn test_render_paragraph_with_left_alignment() {
806 let text = "Hello, world!";
807 let truncated_paragraph = Paragraph::new(text).alignment(Alignment::Left);
808 let wrapped_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: false });
809 let trimmed_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: true });
810
811 for paragraph in [&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph] {
812 test_case(paragraph, &Buffer::with_lines(["Hello, world! "]));
813 test_case(paragraph, &Buffer::with_lines(["Hello, world!"]));
814 }
815
816 test_case(&truncated_paragraph, &Buffer::with_lines(["Hello, wor"]));
817 test_case(
818 &wrapped_paragraph,
819 &Buffer::with_lines(["Hello, ", "world! "]),
820 );
821 test_case(
822 &trimmed_paragraph,
823 &Buffer::with_lines(["Hello, ", "world! "]),
824 );
825 }
826
827 #[test]
828 fn test_render_paragraph_with_center_alignment() {
829 let text = "Hello, world!";
830 let truncated_paragraph = Paragraph::new(text).alignment(Alignment::Center);
831 let wrapped_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: false });
832 let trimmed_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: true });
833
834 for paragraph in [&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph] {
835 test_case(paragraph, &Buffer::with_lines([" Hello, world! "]));
836 test_case(paragraph, &Buffer::with_lines([" Hello, world! "]));
837 test_case(paragraph, &Buffer::with_lines([" Hello, world! "]));
838 test_case(paragraph, &Buffer::with_lines(["Hello, world!"]));
839 }
840
841 test_case(&truncated_paragraph, &Buffer::with_lines(["Hello, wor"]));
842 test_case(
843 &wrapped_paragraph,
844 &Buffer::with_lines([" Hello, ", " world! "]),
845 );
846 test_case(
847 &trimmed_paragraph,
848 &Buffer::with_lines([" Hello, ", " world! "]),
849 );
850 }
851
852 #[test]
853 fn test_render_paragraph_with_right_alignment() {
854 let text = "Hello, world!";
855 let truncated_paragraph = Paragraph::new(text).alignment(Alignment::Right);
856 let wrapped_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: false });
857 let trimmed_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: true });
858
859 for paragraph in [&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph] {
860 test_case(paragraph, &Buffer::with_lines([" Hello, world!"]));
861 test_case(paragraph, &Buffer::with_lines(["Hello, world!"]));
862 }
863
864 test_case(&truncated_paragraph, &Buffer::with_lines(["Hello, wor"]));
865 test_case(
866 &wrapped_paragraph,
867 &Buffer::with_lines([" Hello,", " world!"]),
868 );
869 test_case(
870 &trimmed_paragraph,
871 &Buffer::with_lines([" Hello,", " world!"]),
872 );
873 }
874
875 #[test]
876 fn test_render_paragraph_with_scroll_offset() {
877 let text = "This is a\ncool\nmultiline\nparagraph.";
878 let truncated_paragraph = Paragraph::new(text).scroll((2, 0));
879 let wrapped_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: false });
880 let trimmed_paragraph = truncated_paragraph.clone().wrap(Wrap { trim: true });
881
882 for paragraph in [&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph] {
883 test_case(
884 paragraph,
885 &Buffer::with_lines(["multiline ", "paragraph. ", " "]),
886 );
887 test_case(paragraph, &Buffer::with_lines(["multiline "]));
888 }
889
890 test_case(
891 &truncated_paragraph.clone().scroll((2, 4)),
892 &Buffer::with_lines(["iline ", "graph. "]),
893 );
894 test_case(
895 &wrapped_paragraph,
896 &Buffer::with_lines(["cool ", "multili", "ne "]),
897 );
898 }
899
900 #[test]
901 fn test_render_paragraph_with_zero_width_area() {
902 let text = "Hello, world!";
903 let area = Rect::new(0, 0, 0, 3);
904
905 for paragraph in [
906 Paragraph::new(text),
907 Paragraph::new(text).wrap(Wrap { trim: false }),
908 Paragraph::new(text).wrap(Wrap { trim: true }),
909 ] {
910 test_case(¶graph, &Buffer::empty(area));
911 test_case(¶graph.clone().scroll((2, 4)), &Buffer::empty(area));
912 }
913 }
914
915 #[test]
916 fn test_render_paragraph_with_zero_height_area() {
917 let text = "Hello, world!";
918 let area = Rect::new(0, 0, 10, 0);
919
920 for paragraph in [
921 Paragraph::new(text),
922 Paragraph::new(text).wrap(Wrap { trim: false }),
923 Paragraph::new(text).wrap(Wrap { trim: true }),
924 ] {
925 test_case(¶graph, &Buffer::empty(area));
926 test_case(¶graph.clone().scroll((2, 4)), &Buffer::empty(area));
927 }
928 }
929
930 #[test]
931 fn test_render_paragraph_with_styled_text() {
932 let text = Line::from(vec![
933 Span::styled("Hello, ", Style::default().fg(Color::Red)),
934 Span::styled("world!", Style::default().fg(Color::Blue)),
935 ]);
936
937 let mut expected_buffer = Buffer::with_lines(["Hello, world!"]);
938 expected_buffer.set_style(
939 Rect::new(0, 0, 7, 1),
940 Style::default().fg(Color::Red).bg(Color::Green),
941 );
942 expected_buffer.set_style(
943 Rect::new(7, 0, 6, 1),
944 Style::default().fg(Color::Blue).bg(Color::Green),
945 );
946
947 for paragraph in [
948 Paragraph::new(text.clone()),
949 Paragraph::new(text.clone()).wrap(Wrap { trim: false }),
950 Paragraph::new(text.clone()).wrap(Wrap { trim: true }),
951 ] {
952 test_case(
953 ¶graph.style(Style::default().bg(Color::Green)),
954 &expected_buffer,
955 );
956 }
957 }
958
959 #[test]
960 fn test_render_paragraph_with_special_characters() {
961 let text = "Hello, <world>!";
962 for paragraph in [
963 Paragraph::new(text),
964 Paragraph::new(text).wrap(Wrap { trim: false }),
965 Paragraph::new(text).wrap(Wrap { trim: true }),
966 ] {
967 test_case(¶graph, &Buffer::with_lines(["Hello, <world>!"]));
968 test_case(¶graph, &Buffer::with_lines(["Hello, <world>! "]));
969 test_case(
970 ¶graph,
971 &Buffer::with_lines(["Hello, <world>! ", " "]),
972 );
973 test_case(
974 ¶graph,
975 &Buffer::with_lines(["Hello, <world>!", " "]),
976 );
977 }
978 }
979
980 #[test]
981 fn test_render_paragraph_with_unicode_characters() {
982 let text = "こんにちは, 世界! 😃";
983 let truncated_paragraph = Paragraph::new(text);
984 let wrapped_paragraph = Paragraph::new(text).wrap(Wrap { trim: false });
985 let trimmed_paragraph = Paragraph::new(text).wrap(Wrap { trim: true });
986
987 for paragraph in [&truncated_paragraph, &wrapped_paragraph, &trimmed_paragraph] {
988 test_case(paragraph, &Buffer::with_lines(["こんにちは, 世界! 😃"]));
989 test_case(
990 paragraph,
991 &Buffer::with_lines(["こんにちは, 世界! 😃 "]),
992 );
993 }
994
995 test_case(
996 &truncated_paragraph,
997 &Buffer::with_lines(["こんにちは, 世 "]),
998 );
999 test_case(
1000 &wrapped_paragraph,
1001 &Buffer::with_lines(["こんにちは, ", "世界! 😃 "]),
1002 );
1003 test_case(
1004 &trimmed_paragraph,
1005 &Buffer::with_lines(["こんにちは, ", "世界! 😃 "]),
1006 );
1007 }
1008
1009 #[test]
1010 fn can_be_stylized() {
1011 assert_eq!(
1012 Paragraph::new("").black().on_white().bold().not_dim().style,
1013 Style::default()
1014 .fg(Color::Black)
1015 .bg(Color::White)
1016 .add_modifier(Modifier::BOLD)
1017 .remove_modifier(Modifier::DIM)
1018 );
1019 }
1020
1021 #[test]
1022 fn widgets_paragraph_count_rendered_lines() {
1023 let paragraph = Paragraph::new("Hello World");
1024 assert_eq!(paragraph.line_count(20), 1);
1025 assert_eq!(paragraph.line_count(10), 1);
1026 let paragraph = Paragraph::new("Hello World").wrap(Wrap { trim: false });
1027 assert_eq!(paragraph.line_count(20), 1);
1028 assert_eq!(paragraph.line_count(10), 2);
1029 let paragraph = Paragraph::new("Hello World").wrap(Wrap { trim: true });
1030 assert_eq!(paragraph.line_count(20), 1);
1031 assert_eq!(paragraph.line_count(10), 2);
1032
1033 let text = "Hello World ".repeat(100);
1034 let paragraph = Paragraph::new(text.trim());
1035 assert_eq!(paragraph.line_count(11), 1);
1036 assert_eq!(paragraph.line_count(6), 1);
1037 let paragraph = paragraph.wrap(Wrap { trim: false });
1038 assert_eq!(paragraph.line_count(11), 100);
1039 assert_eq!(paragraph.line_count(6), 200);
1040 let paragraph = paragraph.wrap(Wrap { trim: true });
1041 assert_eq!(paragraph.line_count(11), 100);
1042 assert_eq!(paragraph.line_count(6), 200);
1043 }
1044
1045 #[test]
1046 fn widgets_paragraph_rendered_line_count_accounts_block() {
1047 let block = Block::new();
1048 let paragraph = Paragraph::new("Hello World").block(block);
1049 assert_eq!(paragraph.line_count(20), 1);
1050 assert_eq!(paragraph.line_count(10), 1);
1051
1052 let block = Block::new().borders(Borders::TOP);
1053 let paragraph = paragraph.block(block);
1054 assert_eq!(paragraph.line_count(20), 2);
1055 assert_eq!(paragraph.line_count(10), 2);
1056
1057 let block = Block::new().borders(Borders::BOTTOM);
1058 let paragraph = paragraph.block(block);
1059 assert_eq!(paragraph.line_count(20), 2);
1060 assert_eq!(paragraph.line_count(10), 2);
1061
1062 let block = Block::new().borders(Borders::TOP | Borders::BOTTOM);
1063 let paragraph = paragraph.block(block);
1064 assert_eq!(paragraph.line_count(20), 3);
1065 assert_eq!(paragraph.line_count(10), 3);
1066
1067 let block = Block::bordered();
1068 let paragraph = paragraph.block(block);
1069 assert_eq!(paragraph.line_count(20), 3);
1070 assert_eq!(paragraph.line_count(10), 3);
1071
1072 let block = Block::bordered();
1073 let paragraph = paragraph.block(block).wrap(Wrap { trim: true });
1074 assert_eq!(paragraph.line_count(20), 3);
1075 assert_eq!(paragraph.line_count(10), 4);
1076
1077 let block = Block::bordered();
1078 let paragraph = paragraph.block(block).wrap(Wrap { trim: false });
1079 assert_eq!(paragraph.line_count(20), 3);
1080 assert_eq!(paragraph.line_count(10), 4);
1081
1082 let text = "Hello World ".repeat(100);
1083 let block = Block::new();
1084 let paragraph = Paragraph::new(text.trim()).block(block);
1085 assert_eq!(paragraph.line_count(11), 1);
1086
1087 let block = Block::bordered();
1088 let paragraph = paragraph.block(block);
1089 assert_eq!(paragraph.line_count(11), 3);
1090 assert_eq!(paragraph.line_count(6), 3);
1091
1092 let block = Block::new().borders(Borders::TOP);
1093 let paragraph = paragraph.block(block);
1094 assert_eq!(paragraph.line_count(11), 2);
1095 assert_eq!(paragraph.line_count(6), 2);
1096
1097 let block = Block::new().borders(Borders::BOTTOM);
1098 let paragraph = paragraph.block(block);
1099 assert_eq!(paragraph.line_count(11), 2);
1100 assert_eq!(paragraph.line_count(6), 2);
1101
1102 let block = Block::new().borders(Borders::LEFT | Borders::RIGHT);
1103 let paragraph = paragraph.block(block);
1104 assert_eq!(paragraph.line_count(11), 1);
1105 assert_eq!(paragraph.line_count(6), 1);
1106 }
1107
1108 #[test]
1109 fn widgets_paragraph_line_width() {
1110 let paragraph = Paragraph::new("Hello World");
1111 assert_eq!(paragraph.line_width(), 11);
1112 let paragraph = Paragraph::new("Hello World").wrap(Wrap { trim: false });
1113 assert_eq!(paragraph.line_width(), 11);
1114 let paragraph = Paragraph::new("Hello World").wrap(Wrap { trim: true });
1115 assert_eq!(paragraph.line_width(), 11);
1116
1117 let text = "Hello World ".repeat(100);
1118 let paragraph = Paragraph::new(text);
1119 assert_eq!(paragraph.line_width(), 1200);
1120 let paragraph = paragraph.wrap(Wrap { trim: false });
1121 assert_eq!(paragraph.line_width(), 1200);
1122 let paragraph = paragraph.wrap(Wrap { trim: true });
1123 assert_eq!(paragraph.line_width(), 1200);
1124 }
1125
1126 #[test]
1127 fn widgets_paragraph_line_width_accounts_for_block() {
1128 let block = Block::bordered();
1129 let paragraph = Paragraph::new("Hello World").block(block);
1130 assert_eq!(paragraph.line_width(), 13);
1131
1132 let block = Block::new().borders(Borders::LEFT);
1133 let paragraph = Paragraph::new("Hello World").block(block);
1134 assert_eq!(paragraph.line_width(), 12);
1135
1136 let block = Block::new().borders(Borders::LEFT);
1137 let paragraph = Paragraph::new("Hello World")
1138 .block(block)
1139 .wrap(Wrap { trim: true });
1140 assert_eq!(paragraph.line_width(), 12);
1141
1142 let block = Block::new().borders(Borders::LEFT);
1143 let paragraph = Paragraph::new("Hello World")
1144 .block(block)
1145 .wrap(Wrap { trim: false });
1146 assert_eq!(paragraph.line_width(), 12);
1147 }
1148
1149 #[test]
1150 fn left_aligned() {
1151 let p = Paragraph::new("Hello, world!").left_aligned();
1152 assert_eq!(p.alignment, Alignment::Left);
1153 }
1154
1155 #[test]
1156 fn centered() {
1157 let p = Paragraph::new("Hello, world!").centered();
1158 assert_eq!(p.alignment, Alignment::Center);
1159 }
1160
1161 #[test]
1162 fn right_aligned() {
1163 let p = Paragraph::new("Hello, world!").right_aligned();
1164 assert_eq!(p.alignment, Alignment::Right);
1165 }
1166
1167 #[test]
1172 fn paragraph_block_text_style() {
1173 let text = Text::styled("Styled text", Color::Green);
1174 let paragraph = Paragraph::new(text).block(Block::bordered());
1175
1176 let mut buf = Buffer::empty(Rect::new(0, 0, 20, 3));
1177 paragraph.render(Rect::new(0, 0, 20, 3), &mut buf);
1178
1179 let mut expected = Buffer::with_lines([
1180 "┌──────────────────┐",
1181 "│Styled text │",
1182 "└──────────────────┘",
1183 ]);
1184 expected.set_style(Rect::new(1, 1, 11, 1), Style::default().fg(Color::Green));
1185 assert_eq!(buf, expected);
1186 }
1187}