|
|
|
@ -20,18 +20,22 @@ pub struct WordWrapper<'a, 'b> {
|
|
|
|
|
max_line_width: u16,
|
|
|
|
|
current_line: Vec<Styled<'a>>,
|
|
|
|
|
next_line: Vec<Styled<'a>>,
|
|
|
|
|
/// Removes the leading whitespace from lines
|
|
|
|
|
trim: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a, 'b> WordWrapper<'a, 'b> {
|
|
|
|
|
pub fn new(
|
|
|
|
|
symbols: &'b mut dyn Iterator<Item = Styled<'a>>,
|
|
|
|
|
max_line_width: u16,
|
|
|
|
|
trim: bool,
|
|
|
|
|
) -> WordWrapper<'a, 'b> {
|
|
|
|
|
WordWrapper {
|
|
|
|
|
symbols,
|
|
|
|
|
max_line_width,
|
|
|
|
|
current_line: vec![],
|
|
|
|
|
next_line: vec![],
|
|
|
|
|
trim,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -60,8 +64,8 @@ impl<'a, 'b> LineComposer<'a> for WordWrapper<'a, 'b> {
|
|
|
|
|
|
|
|
|
|
// Ignore characters wider that the total max width.
|
|
|
|
|
if symbol.width() as u16 > self.max_line_width
|
|
|
|
|
// Skip leading whitespace.
|
|
|
|
|
|| symbol_whitespace && symbol != "\n" && current_line_width == 0
|
|
|
|
|
// Skip leading whitespace when trim is enabled.
|
|
|
|
|
|| self.trim && symbol_whitespace && symbol != "\n" && current_line_width == 0
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
@ -233,7 +237,7 @@ mod test {
|
|
|
|
|
use unicode_segmentation::UnicodeSegmentation;
|
|
|
|
|
|
|
|
|
|
enum Composer {
|
|
|
|
|
WordWrapper,
|
|
|
|
|
WordWrapper { trim: bool },
|
|
|
|
|
LineTruncator,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -241,7 +245,9 @@ mod test {
|
|
|
|
|
let style = Default::default();
|
|
|
|
|
let mut styled = UnicodeSegmentation::graphemes(text, true).map(|g| Styled(g, style));
|
|
|
|
|
let mut composer: Box<dyn LineComposer> = match which {
|
|
|
|
|
Composer::WordWrapper => Box::new(WordWrapper::new(&mut styled, text_area_width)),
|
|
|
|
|
Composer::WordWrapper { trim } => {
|
|
|
|
|
Box::new(WordWrapper::new(&mut styled, text_area_width, trim))
|
|
|
|
|
}
|
|
|
|
|
Composer::LineTruncator => Box::new(LineTruncator::new(&mut styled, text_area_width)),
|
|
|
|
|
};
|
|
|
|
|
let mut lines = vec![];
|
|
|
|
@ -263,7 +269,8 @@ mod test {
|
|
|
|
|
let width = 40;
|
|
|
|
|
for i in 1..width {
|
|
|
|
|
let text = "a".repeat(i);
|
|
|
|
|
let (word_wrapper, _) = run_composer(Composer::WordWrapper, &text, width as u16);
|
|
|
|
|
let (word_wrapper, _) =
|
|
|
|
|
run_composer(Composer::WordWrapper { trim: true }, &text, width as u16);
|
|
|
|
|
let (line_truncator, _) = run_composer(Composer::LineTruncator, &text, width as u16);
|
|
|
|
|
let expected = vec![text];
|
|
|
|
|
assert_eq!(word_wrapper, expected);
|
|
|
|
@ -276,7 +283,7 @@ mod test {
|
|
|
|
|
let width = 20;
|
|
|
|
|
let text =
|
|
|
|
|
"abcdefg\nhijklmno\npabcdefg\nhijklmn\nopabcdefghijk\nlmnopabcd\n\n\nefghijklmno";
|
|
|
|
|
let (word_wrapper, _) = run_composer(Composer::WordWrapper, text, width);
|
|
|
|
|
let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
|
|
|
|
|
let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
|
|
|
|
|
|
|
|
|
|
let wrapped: Vec<&str> = text.split('\n').collect();
|
|
|
|
@ -288,7 +295,8 @@ mod test {
|
|
|
|
|
fn line_composer_long_word() {
|
|
|
|
|
let width = 20;
|
|
|
|
|
let text = "abcdefghijklmnopabcdefghijklmnopabcdefghijklmnopabcdefghijklmno";
|
|
|
|
|
let (word_wrapper, _) = run_composer(Composer::WordWrapper, text, width as u16);
|
|
|
|
|
let (word_wrapper, _) =
|
|
|
|
|
run_composer(Composer::WordWrapper { trim: true }, text, width as u16);
|
|
|
|
|
let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width as u16);
|
|
|
|
|
|
|
|
|
|
let wrapped = vec![
|
|
|
|
@ -299,7 +307,7 @@ mod test {
|
|
|
|
|
];
|
|
|
|
|
assert_eq!(
|
|
|
|
|
word_wrapper, wrapped,
|
|
|
|
|
"WordWrapper should deect the line cannot be broken on word boundary and \
|
|
|
|
|
"WordWrapper should detect the line cannot be broken on word boundary and \
|
|
|
|
|
break it at line width limit."
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(line_truncator, vec![&text[..width]]);
|
|
|
|
@ -314,9 +322,12 @@ mod test {
|
|
|
|
|
"abcd efghij klmnopabcd efgh ijklmnopabcdefg hijkl mnopab c d e f g h i j k l \
|
|
|
|
|
m n o";
|
|
|
|
|
let (word_wrapper_single_space, _) =
|
|
|
|
|
run_composer(Composer::WordWrapper, text, width as u16);
|
|
|
|
|
let (word_wrapper_multi_space, _) =
|
|
|
|
|
run_composer(Composer::WordWrapper, text_multi_space, width as u16);
|
|
|
|
|
run_composer(Composer::WordWrapper { trim: true }, text, width as u16);
|
|
|
|
|
let (word_wrapper_multi_space, _) = run_composer(
|
|
|
|
|
Composer::WordWrapper { trim: true },
|
|
|
|
|
text_multi_space,
|
|
|
|
|
width as u16,
|
|
|
|
|
);
|
|
|
|
|
let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width as u16);
|
|
|
|
|
|
|
|
|
|
let word_wrapped = vec![
|
|
|
|
@ -336,7 +347,7 @@ mod test {
|
|
|
|
|
fn line_composer_zero_width() {
|
|
|
|
|
let width = 0;
|
|
|
|
|
let text = "abcd efghij klmnopabcd efgh ijklmnopabcdefg hijkl mnopab ";
|
|
|
|
|
let (word_wrapper, _) = run_composer(Composer::WordWrapper, text, width);
|
|
|
|
|
let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
|
|
|
|
|
let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
|
|
|
|
|
|
|
|
|
|
let expected: Vec<&str> = Vec::new();
|
|
|
|
@ -348,7 +359,7 @@ mod test {
|
|
|
|
|
fn line_composer_max_line_width_of_1() {
|
|
|
|
|
let width = 1;
|
|
|
|
|
let text = "abcd efghij klmnopabcd efgh ijklmnopabcdefg hijkl mnopab ";
|
|
|
|
|
let (word_wrapper, _) = run_composer(Composer::WordWrapper, text, width);
|
|
|
|
|
let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
|
|
|
|
|
let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
|
|
|
|
|
|
|
|
|
|
let expected: Vec<&str> = UnicodeSegmentation::graphemes(text, true)
|
|
|
|
@ -363,7 +374,7 @@ mod test {
|
|
|
|
|
let width = 1;
|
|
|
|
|
let text = "コンピュータ上で文字を扱う場合、典型的には文字\naaaによる通信を行う場合にその\
|
|
|
|
|
両端点では、";
|
|
|
|
|
let (word_wrapper, _) = run_composer(Composer::WordWrapper, text, width);
|
|
|
|
|
let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
|
|
|
|
|
let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
|
|
|
|
|
assert_eq!(word_wrapper, vec!["", "a", "a", "a"]);
|
|
|
|
|
assert_eq!(line_truncator, vec!["", "a"]);
|
|
|
|
@ -374,7 +385,7 @@ mod test {
|
|
|
|
|
fn line_composer_word_wrapper_mixed_length() {
|
|
|
|
|
let width = 20;
|
|
|
|
|
let text = "abcd efghij klmnopabcdefghijklmnopabcdefghijkl mnopab cdefghi j klmno";
|
|
|
|
|
let (word_wrapper, _) = run_composer(Composer::WordWrapper, text, width);
|
|
|
|
|
let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
word_wrapper,
|
|
|
|
|
vec![
|
|
|
|
@ -392,7 +403,8 @@ mod test {
|
|
|
|
|
let width = 20;
|
|
|
|
|
let text = "コンピュータ上で文字を扱う場合、典型的には文字による通信を行う場合にその両端点\
|
|
|
|
|
では、";
|
|
|
|
|
let (word_wrapper, word_wrapper_width) = run_composer(Composer::WordWrapper, &text, width);
|
|
|
|
|
let (word_wrapper, word_wrapper_width) =
|
|
|
|
|
run_composer(Composer::WordWrapper { trim: true }, &text, width);
|
|
|
|
|
let (line_truncator, _) = run_composer(Composer::LineTruncator, &text, width);
|
|
|
|
|
assert_eq!(line_truncator, vec!["コンピュータ上で文字"]);
|
|
|
|
|
let wrapped = vec![
|
|
|
|
@ -410,7 +422,7 @@ mod test {
|
|
|
|
|
fn line_composer_leading_whitespace_removal() {
|
|
|
|
|
let width = 20;
|
|
|
|
|
let text = "AAAAAAAAAAAAAAAAAAAA AAA";
|
|
|
|
|
let (word_wrapper, _) = run_composer(Composer::WordWrapper, text, width);
|
|
|
|
|
let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
|
|
|
|
|
let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
|
|
|
|
|
assert_eq!(word_wrapper, vec!["AAAAAAAAAAAAAAAAAAAA", "AAA",]);
|
|
|
|
|
assert_eq!(line_truncator, vec!["AAAAAAAAAAAAAAAAAAAA"]);
|
|
|
|
@ -421,7 +433,7 @@ mod test {
|
|
|
|
|
fn line_composer_lots_of_spaces() {
|
|
|
|
|
let width = 20;
|
|
|
|
|
let text = " ";
|
|
|
|
|
let (word_wrapper, _) = run_composer(Composer::WordWrapper, text, width);
|
|
|
|
|
let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
|
|
|
|
|
let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
|
|
|
|
|
assert_eq!(word_wrapper, vec![""]);
|
|
|
|
|
assert_eq!(line_truncator, vec![" "]);
|
|
|
|
@ -433,7 +445,7 @@ mod test {
|
|
|
|
|
fn line_composer_char_plus_lots_of_spaces() {
|
|
|
|
|
let width = 20;
|
|
|
|
|
let text = "a ";
|
|
|
|
|
let (word_wrapper, _) = run_composer(Composer::WordWrapper, text, width);
|
|
|
|
|
let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
|
|
|
|
|
let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
|
|
|
|
|
// What's happening below is: the first line gets consumed, trailing spaces discarded,
|
|
|
|
|
// after 20 of which a word break occurs (probably shouldn't). The second line break
|
|
|
|
@ -452,7 +464,8 @@ mod test {
|
|
|
|
|
// hiragana and katakana...
|
|
|
|
|
// This happens to also be a test case for mixed width because regular spaces are single width.
|
|
|
|
|
let text = "コンピュ ータ上で文字を扱う場合、 典型的には文 字による 通信を行 う場合にその両端点では、";
|
|
|
|
|
let (word_wrapper, word_wrapper_width) = run_composer(Composer::WordWrapper, text, width);
|
|
|
|
|
let (word_wrapper, word_wrapper_width) =
|
|
|
|
|
run_composer(Composer::WordWrapper { trim: true }, text, width);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
word_wrapper,
|
|
|
|
|
vec![
|
|
|
|
@ -473,12 +486,50 @@ mod test {
|
|
|
|
|
fn line_composer_word_wrapper_nbsp() {
|
|
|
|
|
let width = 20;
|
|
|
|
|
let text = "AAAAAAAAAAAAAAA AAAA\u{00a0}AAA";
|
|
|
|
|
let (word_wrapper, _) = run_composer(Composer::WordWrapper, text, width);
|
|
|
|
|
let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
|
|
|
|
|
assert_eq!(word_wrapper, vec!["AAAAAAAAAAAAAAA", "AAAA\u{00a0}AAA",]);
|
|
|
|
|
|
|
|
|
|
// Ensure that if the character was a regular space, it would be wrapped differently.
|
|
|
|
|
let text_space = text.replace("\u{00a0}", " ");
|
|
|
|
|
let (word_wrapper_space, _) = run_composer(Composer::WordWrapper, &text_space, width);
|
|
|
|
|
let (word_wrapper_space, _) =
|
|
|
|
|
run_composer(Composer::WordWrapper { trim: true }, &text_space, width);
|
|
|
|
|
assert_eq!(word_wrapper_space, vec!["AAAAAAAAAAAAAAA AAAA", "AAA",]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn line_composer_word_wrapper_preserve_indentation() {
|
|
|
|
|
let width = 20;
|
|
|
|
|
let text = "AAAAAAAAAAAAAAAAAAAA AAA";
|
|
|
|
|
let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: false }, text, width);
|
|
|
|
|
assert_eq!(word_wrapper, vec!["AAAAAAAAAAAAAAAAAAAA", " AAA",]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn line_composer_word_wrapper_preserve_indentation_with_wrap() {
|
|
|
|
|
let width = 10;
|
|
|
|
|
let text = "AAA AAA AAAAA AA AAAAAA\n B\n C\n D";
|
|
|
|
|
let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: false }, text, width);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
word_wrapper,
|
|
|
|
|
vec!["AAA AAA", "AAAAA AA", "AAAAAA", " B", " C", " D"]
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn line_composer_word_wrapper_preserve_indentation_lots_of_whitespace() {
|
|
|
|
|
let width = 10;
|
|
|
|
|
let text = " 4 Indent\n must wrap!";
|
|
|
|
|
let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: false }, text, width);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
word_wrapper,
|
|
|
|
|
vec![
|
|
|
|
|
" ",
|
|
|
|
|
" 4",
|
|
|
|
|
"Indent",
|
|
|
|
|
" ",
|
|
|
|
|
" must",
|
|
|
|
|
"wrap!"
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|