Skip to content

Commit 8bfd666

Browse files
authored
feat(Paragraph): add line_count and line_width unstable helper methods
This is an unstable feature that may be removed in the future
1 parent 3ec4e24 commit 8bfd666

File tree

2 files changed

+120
-1
lines changed

2 files changed

+120
-1
lines changed

Cargo.toml

+7-1
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,18 @@ underline-color = ["dep:crossterm"]
9393
#! The following features are unstable and may change in the future:
9494

9595
## Enable all unstable features.
96-
unstable = ["unstable-segment-size"]
96+
unstable = ["unstable-segment-size", "unstable-rendered-line-info"]
9797

9898
## Enables the [`Layout::segment_size`](crate::layout::Layout::segment_size) method which is experimental and may change in the
9999
## future. See [Issue #536](https://github.com/ratatui-org/ratatui/issues/536) for more details.
100100
unstable-segment-size = []
101101

102+
## Enables the [`Paragraph::line_count`](crate::widgets::Paragraph::line_count)
103+
## [`Paragraph::line_width`](crate::widgets::Paragraph::line_width) methods
104+
## which are experimental and may change in the future.
105+
## See [Issue 293](https://github.com/ratatui-org/ratatui/issues/293) for more details.
106+
unstable-rendered-line-info = []
107+
102108
[package.metadata.docs.rs]
103109
all-features = true
104110
# see https://doc.rust-lang.org/nightly/rustdoc/scraped-examples.html

src/widgets/paragraph.rs

+113
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,77 @@ impl<'a> Paragraph<'a> {
213213
self.alignment = alignment;
214214
self
215215
}
216+
217+
/// Calculates the number of lines needed to fully render.
218+
///
219+
/// Given a max line width, this method calculates the number of lines that a paragraph will
220+
/// need in order to be fully rendered. For paragraphs that do not use wrapping, this count is
221+
/// simply the number of lines present in the paragraph.
222+
///
223+
/// # Example
224+
///
225+
/// ```ignore
226+
/// # use ratatui::{prelude::*, widgets::*};
227+
/// let paragraph = Paragraph::new("Hello World")
228+
/// .wrap(Wrap { trim: false });
229+
/// assert_eq!(paragraph.line_count(20), 1);
230+
/// assert_eq!(paragraph.line_count(10), 2);
231+
/// ```
232+
#[stability::unstable(
233+
feature = "rendered-line-info",
234+
reason = "The design for text wrapping is not stable and might affect this API.",
235+
issue = "https://github.com/ratatui-org/ratatui/issues/293"
236+
)]
237+
pub fn line_count(&self, width: u16) -> usize {
238+
if width < 1 {
239+
return 0;
240+
}
241+
242+
if let Some(Wrap { trim }) = self.wrap {
243+
let styled = self.text.lines.iter().map(|line| {
244+
let graphemes = line
245+
.spans
246+
.iter()
247+
.flat_map(|span| span.styled_graphemes(self.style));
248+
let alignment = line.alignment.unwrap_or(self.alignment);
249+
(graphemes, alignment)
250+
});
251+
let mut line_composer = WordWrapper::new(styled, width, trim);
252+
let mut count = 0;
253+
while line_composer.next_line().is_some() {
254+
count += 1;
255+
}
256+
count
257+
} else {
258+
self.text.lines.len()
259+
}
260+
}
261+
262+
/// Calculates the shortest line width needed to avoid any word being wrapped or truncated.
263+
///
264+
/// # Example
265+
///
266+
/// ```ignore
267+
/// # use ratatui::{prelude::*, widgets::*};
268+
/// let paragraph = Paragraph::new("Hello World");
269+
/// assert_eq!(paragraph.line_width(), 11);
270+
///
271+
/// let paragraph = Paragraph::new("Hello World\nhi\nHello World!!!");
272+
/// assert_eq!(paragraph.line_width(), 14);
273+
/// ```
274+
#[stability::unstable(
275+
feature = "rendered-line-info",
276+
reason = "The design for text wrapping is not stable and might affect this API.",
277+
issue = "https://github.com/ratatui-org/ratatui/issues/293"
278+
)]
279+
pub fn line_width(&self) -> usize {
280+
self.text
281+
.lines
282+
.iter()
283+
.map(|l| l.width())
284+
.max()
285+
.unwrap_or_default()
286+
}
216287
}
217288

218289
impl<'a> Widget for Paragraph<'a> {
@@ -815,4 +886,46 @@ mod test {
815886
.remove_modifier(Modifier::DIM)
816887
)
817888
}
889+
890+
#[test]
891+
fn widgets_paragraph_count_rendered_lines() {
892+
let paragraph = Paragraph::new("Hello World");
893+
assert_eq!(paragraph.line_count(20), 1);
894+
assert_eq!(paragraph.line_count(10), 1);
895+
let paragraph = Paragraph::new("Hello World").wrap(Wrap { trim: false });
896+
assert_eq!(paragraph.line_count(20), 1);
897+
assert_eq!(paragraph.line_count(10), 2);
898+
let paragraph = Paragraph::new("Hello World").wrap(Wrap { trim: true });
899+
assert_eq!(paragraph.line_count(20), 1);
900+
assert_eq!(paragraph.line_count(10), 2);
901+
902+
let text = "Hello World ".repeat(100);
903+
let paragraph = Paragraph::new(text.trim());
904+
assert_eq!(paragraph.line_count(11), 1);
905+
assert_eq!(paragraph.line_count(6), 1);
906+
let paragraph = paragraph.wrap(Wrap { trim: false });
907+
assert_eq!(paragraph.line_count(11), 100);
908+
assert_eq!(paragraph.line_count(6), 200);
909+
let paragraph = paragraph.wrap(Wrap { trim: true });
910+
assert_eq!(paragraph.line_count(11), 100);
911+
assert_eq!(paragraph.line_count(6), 200);
912+
}
913+
914+
#[test]
915+
fn widgets_paragraph_line_width() {
916+
let paragraph = Paragraph::new("Hello World");
917+
assert_eq!(paragraph.line_width(), 11);
918+
let paragraph = Paragraph::new("Hello World").wrap(Wrap { trim: false });
919+
assert_eq!(paragraph.line_width(), 11);
920+
let paragraph = Paragraph::new("Hello World").wrap(Wrap { trim: true });
921+
assert_eq!(paragraph.line_width(), 11);
922+
923+
let text = "Hello World ".repeat(100);
924+
let paragraph = Paragraph::new(text);
925+
assert_eq!(paragraph.line_width(), 1200);
926+
let paragraph = paragraph.wrap(Wrap { trim: false });
927+
assert_eq!(paragraph.line_width(), 1200);
928+
let paragraph = paragraph.wrap(Wrap { trim: true });
929+
assert_eq!(paragraph.line_width(), 1200);
930+
}
818931
}

0 commit comments

Comments
 (0)