mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-25 22:20:31 +00:00
chore(wrap): implement trim for CharWrapper
and add some unit tests
This commit is contained in:
parent
b0b1ac85b5
commit
7908e944f6
3 changed files with 135 additions and 7 deletions
|
@ -340,7 +340,7 @@ mod test {
|
|||
Paragraph::new(text).block(Block::default().title("Title").borders(Borders::ALL));
|
||||
let char_wrapped_paragraph = truncated_paragraph
|
||||
.clone()
|
||||
.wrap(Wrap::WordBoundary)
|
||||
.wrap(Wrap::CharBoundary)
|
||||
.trim(false);
|
||||
let word_wrapped_paragraph = truncated_paragraph
|
||||
.clone()
|
||||
|
@ -394,6 +394,15 @@ mod test {
|
|||
"└───────────┘",
|
||||
]),
|
||||
);
|
||||
test_case(
|
||||
&char_wrapped_paragraph,
|
||||
Buffer::with_lines(vec![
|
||||
"┌Title──────┐",
|
||||
"│Hello, worl│",
|
||||
"│d! │",
|
||||
"└───────────┘",
|
||||
]),
|
||||
);
|
||||
test_case(
|
||||
&word_wrapped_paragraph,
|
||||
Buffer::with_lines(vec![
|
||||
|
|
|
@ -7,8 +7,14 @@ use unicode_width::UnicodeWidthStr;
|
|||
use crate::layout::Alignment;
|
||||
use crate::text::StyledGrapheme;
|
||||
|
||||
// NBSP is a non-breaking space which is essentially a whitespace character that is treated
|
||||
// the same as non-whitespace characters in wrapping algorithms
|
||||
const NBSP: &str = "\u{00a0}";
|
||||
|
||||
fn is_whitespace(symbol: &str) -> bool {
|
||||
symbol.chars().all(&char::is_whitespace) && symbol != NBSP
|
||||
}
|
||||
|
||||
/// A state machine to pack styled symbols into lines.
|
||||
/// Cannot implement it as Iterator since it yields slices of the internal buffer (need streaming
|
||||
/// iterators for that).
|
||||
|
@ -59,6 +65,8 @@ where
|
|||
let mut current_line = vec![];
|
||||
let mut current_line_width = 0;
|
||||
|
||||
let mut has_encountered_non_whitespace_this_line = false;
|
||||
|
||||
// Iterate over all characters in the line
|
||||
for StyledGrapheme { symbol, style } in line {
|
||||
let symbol_width = symbol.width() as u16;
|
||||
|
@ -67,8 +75,20 @@ where
|
|||
continue;
|
||||
}
|
||||
|
||||
// If the current line is not empty, we need to check if the current character fits
|
||||
// into the current line
|
||||
let symbol_whitespace = is_whitespace(symbol);
|
||||
|
||||
// If the current character is whitespace and no non-whitespace character has been
|
||||
// encountered yet on this line, skip it
|
||||
if self.trim && !has_encountered_non_whitespace_this_line {
|
||||
if symbol_whitespace {
|
||||
continue;
|
||||
} else {
|
||||
has_encountered_non_whitespace_this_line = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If the current line is not empty, we need to check if the current character
|
||||
// fits into the current line
|
||||
if current_line_width + symbol_width <= self.max_line_width {
|
||||
// If it fits, add it to the current line
|
||||
current_line.push(StyledGrapheme { symbol, style });
|
||||
|
@ -76,7 +96,16 @@ where
|
|||
} else {
|
||||
// If it doesn't fit, wrap the current line and start a new one
|
||||
wrapped_lines.push(current_line);
|
||||
current_line = vec![StyledGrapheme { symbol, style }];
|
||||
current_line = vec![];
|
||||
|
||||
// If the wrapped symbol is whitespace, start trimming whitespace
|
||||
if self.trim && symbol_whitespace {
|
||||
has_encountered_non_whitespace_this_line = false;
|
||||
current_line_width = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
current_line.push(StyledGrapheme { symbol, style });
|
||||
current_line_width = symbol_width;
|
||||
}
|
||||
}
|
||||
|
@ -236,8 +265,7 @@ where
|
|||
|
||||
let mut has_seen_non_whitespace = false;
|
||||
for StyledGrapheme { symbol, style } in line_symbols {
|
||||
let symbol_whitespace =
|
||||
symbol.chars().all(&char::is_whitespace) && symbol != NBSP;
|
||||
let symbol_whitespace = is_whitespace(symbol);
|
||||
let symbol_width = symbol.width() as u16;
|
||||
// Ignore characters wider than the total max width
|
||||
if symbol_width > self.max_line_width {
|
||||
|
|
|
@ -144,7 +144,63 @@ const SAMPLE_STRING: &str = "The library is based on the principle of immediate
|
|||
interactive UI, this may introduce overhead for highly dynamic content.";
|
||||
|
||||
#[test]
|
||||
fn widgets_paragraph_can_wrap_its_content() {
|
||||
fn widgets_paragraph_can_char_wrap_its_content() {
|
||||
let text = vec![Line::from(SAMPLE_STRING)];
|
||||
let paragraph = Paragraph::new(text)
|
||||
.block(Block::default().borders(Borders::ALL))
|
||||
.wrap(Wrap::CharBoundary)
|
||||
.trim(true);
|
||||
|
||||
// If char wrapping is used, all alignments should be the same except on the last line.
|
||||
test_case(
|
||||
paragraph.clone().alignment(Alignment::Left),
|
||||
Buffer::with_lines(vec![
|
||||
"┌──────────────────┐",
|
||||
"│The library is bas│",
|
||||
"│ed on the principl│",
|
||||
"│e of immediate ren│",
|
||||
"│dering with interm│",
|
||||
"│ediate buffers. Th│",
|
||||
"│is means that at e│",
|
||||
"│ach new frame you │",
|
||||
"│should build all w│",
|
||||
"└──────────────────┘",
|
||||
]),
|
||||
);
|
||||
test_case(
|
||||
paragraph.clone().alignment(Alignment::Center),
|
||||
Buffer::with_lines(vec![
|
||||
"┌──────────────────┐",
|
||||
"│The library is bas│",
|
||||
"│ed on the principl│",
|
||||
"│e of immediate ren│",
|
||||
"│dering with interm│",
|
||||
"│ediate buffers. Th│",
|
||||
"│is means that at e│",
|
||||
"│ach new frame you │",
|
||||
"│should build all w│",
|
||||
"└──────────────────┘",
|
||||
]),
|
||||
);
|
||||
test_case(
|
||||
paragraph.clone().alignment(Alignment::Right),
|
||||
Buffer::with_lines(vec![
|
||||
"┌──────────────────┐",
|
||||
"│The library is bas│",
|
||||
"│ed on the principl│",
|
||||
"│e of immediate ren│",
|
||||
"│dering with interm│",
|
||||
"│ediate buffers. Th│",
|
||||
"│is means that at e│",
|
||||
"│ach new frame you │",
|
||||
"│should build all w│",
|
||||
"└──────────────────┘",
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn widgets_paragraph_can_word_wrap_its_content() {
|
||||
let text = vec![Line::from(SAMPLE_STRING)];
|
||||
let paragraph = Paragraph::new(text)
|
||||
.block(Block::default().borders(Borders::ALL))
|
||||
|
@ -198,6 +254,41 @@ fn widgets_paragraph_can_wrap_its_content() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn widgets_paragraph_can_trim_its_content() {
|
||||
let space_text = "This is some text with an excessive amount of whitespace between words.";
|
||||
let text = vec![Line::from(space_text)];
|
||||
let paragraph = Paragraph::new(text)
|
||||
.block(Block::default().borders(Borders::ALL))
|
||||
.wrap(Wrap::CharBoundary);
|
||||
|
||||
test_case(
|
||||
paragraph.clone().alignment(Alignment::Left).trim(true),
|
||||
Buffer::with_lines(vec![
|
||||
"┌──────────────────┐",
|
||||
"│This is some │",
|
||||
"│text with an exces│",
|
||||
"│sive amount │",
|
||||
"│of whitespace │",
|
||||
"│between words. │",
|
||||
"└──────────────────┘",
|
||||
]),
|
||||
);
|
||||
test_case(
|
||||
paragraph.clone().alignment(Alignment::Left).trim(false),
|
||||
Buffer::with_lines(vec![
|
||||
"┌──────────────────┐",
|
||||
"│This is some │",
|
||||
"│ text with an ex│",
|
||||
"│cessive amou│",
|
||||
"│nt of whitespace │",
|
||||
"│ be│",
|
||||
"│tween words. │",
|
||||
"└──────────────────┘",
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn widgets_paragraph_works_with_padding() {
|
||||
let text = vec![Line::from(SAMPLE_STRING)];
|
||||
|
|
Loading…
Reference in a new issue