diff options
author | Stephan Dilly <dilly.stephan@gmail.com> | 2021-08-17 14:24:25 +0200 |
---|---|---|
committer | Stephan Dilly <dilly.stephan@gmail.com> | 2021-08-17 14:24:25 +0200 |
commit | dad8e8d43de79a3c39a37f118e529dc88fab5495 (patch) | |
tree | 32329a450106142d35374c88b776c74a679bd9a0 /src/ui | |
parent | 25a49e22f267f467fb7436c799e1d01294e0a914 (diff) |
cargo fmt: use hardtabs
since it does not support hard-whitespaces its the only way to make whitespaces consisitent and checked
Diffstat (limited to 'src/ui')
-rw-r--r-- | src/ui/mod.rs | 174 | ||||
-rw-r--r-- | src/ui/reflow.rs | 1217 | ||||
-rw-r--r-- | src/ui/scrollbar.rs | 110 | ||||
-rw-r--r-- | src/ui/scrolllist.rs | 112 | ||||
-rw-r--r-- | src/ui/stateful_paragraph.rs | 346 | ||||
-rw-r--r-- | src/ui/style.rs | 556 | ||||
-rw-r--r-- | src/ui/syntax_text.rs | 302 |
7 files changed, 1408 insertions, 1409 deletions
diff --git a/src/ui/mod.rs b/src/ui/mod.rs index ae143085..81158003 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -9,7 +9,7 @@ use filetreelist::MoveSelection; pub use scrollbar::draw_scrollbar; pub use scrolllist::{draw_list, draw_list_block}; pub use stateful_paragraph::{ - ParagraphState, ScrollPos, StatefulParagraph, + ParagraphState, ScrollPos, StatefulParagraph, }; pub use syntax_text::{AsyncSyntaxJob, SyntaxText}; use tui::layout::{Constraint, Direction, Layout, Rect}; @@ -18,123 +18,123 @@ use crate::keys::SharedKeyConfig; /// return the scroll position (line) necessary to have the `selection` in view if it is not already pub const fn calc_scroll_top( - current_top: usize, - height_in_lines: usize, - selection: usize, + current_top: usize, + height_in_lines: usize, + selection: usize, ) -> usize { - if current_top + height_in_lines <= selection { - selection.saturating_sub(height_in_lines) + 1 - } else if current_top > selection { - selection - } else { - current_top - } + if current_top + height_in_lines <= selection { + selection.saturating_sub(height_in_lines) + 1 + } else if current_top > selection { + selection + } else { + current_top + } } /// ui component size representation #[derive(Copy, Clone)] pub struct Size { - pub width: u16, - pub height: u16, + pub width: u16, + pub height: u16, } impl Size { - pub const fn new(width: u16, height: u16) -> Self { - Self { width, height } - } + pub const fn new(width: u16, height: u16) -> Self { + Self { width, height } + } } impl From<Rect> for Size { - fn from(r: Rect) -> Self { - Self { - width: r.width, - height: r.height, - } - } + fn from(r: Rect) -> Self { + Self { + width: r.width, + height: r.height, + } + } } /// use layouts to create a rects that /// centers inside `r` and sizes `percent_x`/`percent_x` of `r` pub fn centered_rect( - percent_x: u16, - percent_y: u16, - r: Rect, + percent_x: u16, + percent_y: u16, + r: Rect, ) -> Rect { - let popup_layout = Layout::default() - .direction(Direction::Vertical) - .constraints( - [ - Constraint::Percentage((100 - percent_y) / 2), - Constraint::Percentage(percent_y), - Constraint::Percentage((100 - percent_y) / 2), - ] - .as_ref(), - ) - .split(r); + let popup_layout = Layout::default() + .direction(Direction::Vertical) + .constraints( + [ + Constraint::Percentage((100 - percent_y) / 2), + Constraint::Percentage(percent_y), + Constraint::Percentage((100 - percent_y) / 2), + ] + .as_ref(), + ) + .split(r); - Layout::default() - .direction(Direction::Horizontal) - .constraints( - [ - Constraint::Percentage((100 - percent_x) / 2), - Constraint::Percentage(percent_x), - Constraint::Percentage((100 - percent_x) / 2), - ] - .as_ref(), - ) - .split(popup_layout[1])[1] + Layout::default() + .direction(Direction::Horizontal) + .constraints( + [ + Constraint::Percentage((100 - percent_x) / 2), + Constraint::Percentage(percent_x), + Constraint::Percentage((100 - percent_x) / 2), + ] + .as_ref(), + ) + .split(popup_layout[1])[1] } /// makes sure Rect `r` at least stays as big as min and not bigger than max pub fn rect_inside(min: Size, max: Size, r: Rect) -> Rect { - let new_width = r.width.max(min.width).min(max.width); - let new_height = r.height.max(min.height).min(max.height); - let diff_width = new_width.saturating_sub(r.width); - let diff_height = new_height.saturating_sub(r.height); + let new_width = r.width.max(min.width).min(max.width); + let new_height = r.height.max(min.height).min(max.height); + let diff_width = new_width.saturating_sub(r.width); + let diff_height = new_height.saturating_sub(r.height); - Rect::new( - r.x.saturating_sub(diff_width / 2), - r.y.saturating_sub(diff_height / 2), - new_width, - new_height, - ) + Rect::new( + r.x.saturating_sub(diff_width / 2), + r.y.saturating_sub(diff_height / 2), + new_width, + new_height, + ) } pub fn centered_rect_absolute( - width: u16, - height: u16, - r: Rect, + width: u16, + height: u16, + r: Rect, ) -> Rect { - Rect::new( - (r.width.saturating_sub(width)) / 2, - (r.height.saturating_sub(height)) / 2, - width.min(r.width), - height.min(r.height), - ) + Rect::new( + (r.width.saturating_sub(width)) / 2, + (r.height.saturating_sub(height)) / 2, + width.min(r.width), + height.min(r.height), + ) } /// pub fn common_nav( - key: crossterm::event::KeyEvent, - key_config: &SharedKeyConfig, + key: crossterm::event::KeyEvent, + key_config: &SharedKeyConfig, ) -> Option<MoveSelection> { - if key == key_config.move_down { - Some(MoveSelection::Down) - } else if key == key_config.move_up { - Some(MoveSelection::Up) - } else if key == key_config.page_up { - Some(MoveSelection::PageUp) - } else if key == key_config.page_down { - Some(MoveSelection::PageDown) - } else if key == key_config.move_right { - Some(MoveSelection::Right) - } else if key == key_config.move_left { - Some(MoveSelection::Left) - } else if key == key_config.home || key == key_config.shift_up { - Some(MoveSelection::Top) - } else if key == key_config.end || key == key_config.shift_down { - Some(MoveSelection::End) - } else { - None - } + if key == key_config.move_down { + Some(MoveSelection::Down) + } else if key == key_config.move_up { + Some(MoveSelection::Up) + } else if key == key_config.page_up { + Some(MoveSelection::PageUp) + } else if key == key_config.page_down { + Some(MoveSelection::PageDown) + } else if key == key_config.move_right { + Some(MoveSelection::Right) + } else if key == key_config.move_left { + Some(MoveSelection::Left) + } else if key == key_config.home || key == key_config.shift_up { + Some(MoveSelection::Top) + } else if key == key_config.end || key == key_config.shift_down { + Some(MoveSelection::End) + } else { + None + } } diff --git a/src/ui/reflow.rs b/src/ui/reflow.rs index 3d469c52..b2417867 100644 --- a/src/ui/reflow.rs +++ b/src/ui/reflow.rs @@ -9,655 +9,654 @@ const NBSP: &str = "\u{00a0}"; /// Cannot implement it as Iterator since it yields slices of the internal buffer (need streaming /// iterators for that). pub trait LineComposer<'a> { - fn next_line(&mut self) -> Option<(&[StyledGrapheme<'a>], u16)>; + fn next_line(&mut self) -> Option<(&[StyledGrapheme<'a>], u16)>; } /// A state machine that wraps lines on word boundaries. pub struct WordWrapper<'a, 'b> { - symbols: &'b mut dyn Iterator<Item = StyledGrapheme<'a>>, - max_line_width: u16, - current_line: Vec<StyledGrapheme<'a>>, - next_line: Vec<StyledGrapheme<'a>>, - /// Removes the leading whitespace from lines - trim: bool, + symbols: &'b mut dyn Iterator<Item = StyledGrapheme<'a>>, + max_line_width: u16, + current_line: Vec<StyledGrapheme<'a>>, + next_line: Vec<StyledGrapheme<'a>>, + /// Removes the leading whitespace from lines + trim: bool, } impl<'a, 'b> WordWrapper<'a, 'b> { - pub fn new( - symbols: &'b mut dyn Iterator<Item = StyledGrapheme<'a>>, - max_line_width: u16, - trim: bool, - ) -> WordWrapper<'a, 'b> { - WordWrapper { - symbols, - max_line_width, - current_line: vec![], - next_line: vec![], - trim, - } - } + pub fn new( + symbols: &'b mut dyn Iterator<Item = StyledGrapheme<'a>>, + max_line_width: u16, + trim: bool, + ) -> WordWrapper<'a, 'b> { + WordWrapper { + symbols, + max_line_width, + current_line: vec![], + next_line: vec![], + trim, + } + } } impl<'a, 'b> LineComposer<'a> for WordWrapper<'a, 'b> { - fn next_line(&mut self) -> Option<(&[StyledGrapheme<'a>], u16)> { - if self.max_line_width == 0 { - return None; - } - std::mem::swap(&mut self.current_line, &mut self.next_line); - self.next_line.truncate(0); - - let mut current_line_width = self - .current_line - .iter() - .map(|StyledGrapheme { symbol, .. }| -> u16 { - symbol.width().cast() - }) - .sum(); - - let mut symbols_to_last_word_end: usize = 0; - let mut width_to_last_word_end: u16 = 0; - let mut prev_whitespace = false; - let mut symbols_exhausted = true; - for StyledGrapheme { symbol, style } in &mut self.symbols { - symbols_exhausted = false; - let symbol_whitespace = - symbol.chars().all(&char::is_whitespace) - && symbol != NBSP; - - // Ignore characters wider that the total max width. - if Cast::<u16>::cast(symbol.width()) > self.max_line_width + fn next_line(&mut self) -> Option<(&[StyledGrapheme<'a>], u16)> { + if self.max_line_width == 0 { + return None; + } + std::mem::swap(&mut self.current_line, &mut self.next_line); + self.next_line.truncate(0); + + let mut current_line_width = self + .current_line + .iter() + .map(|StyledGrapheme { symbol, .. }| -> u16 { + symbol.width().cast() + }) + .sum(); + + let mut symbols_to_last_word_end: usize = 0; + let mut width_to_last_word_end: u16 = 0; + let mut prev_whitespace = false; + let mut symbols_exhausted = true; + for StyledGrapheme { symbol, style } in &mut self.symbols { + symbols_exhausted = false; + let symbol_whitespace = + symbol.chars().all(&char::is_whitespace) + && symbol != NBSP; + + // Ignore characters wider that the total max width. + if Cast::<u16>::cast(symbol.width()) > self.max_line_width // Skip leading whitespace when trim is enabled. || self.trim && symbol_whitespace && symbol != "\n" && current_line_width == 0 - { - continue; - } - - // Break on newline and discard it. - if symbol == "\n" { - if prev_whitespace { - current_line_width = width_to_last_word_end; - self.current_line - .truncate(symbols_to_last_word_end); - } - break; - } - - // Mark the previous symbol as word end. - if symbol_whitespace && !prev_whitespace { - symbols_to_last_word_end = self.current_line.len(); - width_to_last_word_end = current_line_width; - } - - self.current_line.push(StyledGrapheme { symbol, style }); - current_line_width += Cast::<u16>::cast(symbol.width()); - - if current_line_width > self.max_line_width { - // If there was no word break in the text, wrap at the end of the line. - let (truncate_at, truncated_width) = - if symbols_to_last_word_end == 0 { - ( - self.current_line.len() - 1, - self.max_line_width, - ) - } else { - ( - symbols_to_last_word_end, - width_to_last_word_end, - ) - }; - - // Push the remainder to the next line but strip leading whitespace: - { - let remainder = &self.current_line[truncate_at..]; - if let Some(remainder_nonwhite) = - remainder.iter().position( - |StyledGrapheme { symbol, .. }| { - !symbol - .chars() - .all(&char::is_whitespace) - }, - ) - { - self.next_line.extend_from_slice( - &remainder[remainder_nonwhite..], - ); - } - } - self.current_line.truncate(truncate_at); - current_line_width = truncated_width; - break; - } - - prev_whitespace = symbol_whitespace; - } - - // Even if the iterator is exhausted, pass the previous remainder. - if symbols_exhausted && self.current_line.is_empty() { - None - } else { - Some((&self.current_line[..], current_line_width)) - } - } + { + continue; + } + + // Break on newline and discard it. + if symbol == "\n" { + if prev_whitespace { + current_line_width = width_to_last_word_end; + self.current_line + .truncate(symbols_to_last_word_end); + } + break; + } + + // Mark the previous symbol as word end. + if symbol_whitespace && !prev_whitespace { + symbols_to_last_word_end = self.current_line.len(); + width_to_last_word_end = current_line_width; + } + + self.current_line.push(StyledGrapheme { symbol, style }); + current_line_width += Cast::<u16>::cast(symbol.width()); + + if current_line_width > self.max_line_width { + // If there was no word break in the text, wrap at the end of the line. + let (truncate_at, truncated_width) = + if symbols_to_last_word_end == 0 { + ( + self.current_line.len() - 1, + self.max_line_width, + ) + } else { + ( + symbols_to_last_word_end, + width_to_last_word_end, + ) + }; + + // Push the remainder to the next line but strip leading whitespace: + { + let remainder = &self.current_line[truncate_at..]; + if let Some(remainder_nonwhite) = + remainder.iter().position( + |StyledGrapheme { symbol, .. }| { + !symbol + .chars() + .all(&char::is_whitespace) + }, + ) { + self.next_line.extend_from_slice( + &remainder[remainder_nonwhite..], + ); + } + } + self.current_line.truncate(truncate_at); + current_line_width = truncated_width; + break; + } + + prev_whitespace = symbol_whitespace; + } + + // Even if the iterator is exhausted, pass the previous remainder. + if symbols_exhausted && self.current_line.is_empty() { + None + } else { + Some((&self.current_line[..], current_line_width)) + } + } } /// A state machine that truncates overhanging lines. pub struct LineTruncator<'a, 'b> { - symbols: &'b mut dyn Iterator<Item = StyledGrapheme<'a>>, - max_line_width: u16, - current_line: Vec<StyledGrapheme<'a>>, - /// Record the offet to skip render - horizontal_offset: u16, + symbols: &'b mut dyn Iterator<Item = StyledGrapheme<'a>>, + max_line_width: u16, + current_line: Vec<StyledGrapheme<'a>>, + /// Record the offet to skip render + horizontal_offset: u16, } impl<'a, 'b> LineTruncator<'a, 'b> { - pub fn new( - symbols: &'b mut dyn Iterator<Item = StyledGrapheme<'a>>, - max_line_width: u16, - ) -> LineTruncator<'a, 'b> { - LineTruncator { - symbols, - max_line_width, - horizontal_offset: 0, - current_line: vec![], - } - } - - pub fn set_horizontal_offset(&mut self, horizontal_offset: u16) { - self.horizontal_offset = horizontal_offset; - } + pub fn new( + symbols: &'b mut dyn Iterator<Item = StyledGrapheme<'a>>, + max_line_width: u16, + ) -> LineTruncator<'a, 'b> { + LineTruncator { + symbols, + max_line_width, + horizontal_offset: 0, + current_line: vec![], + } + } + + pub fn set_horizontal_offset(&mut self, horizontal_offset: u16) { + self.horizontal_offset = horizontal_offset; + } } impl<'a, 'b> LineComposer<'a> for LineTruncator<'a, 'b> { - fn next_line(&mut self) -> Option<(&[StyledGrapheme<'a>], u16)> { - if self.max_line_width == 0 { - return None; - } - - self.current_line.truncate(0); - let mut current_line_width = 0; - - let mut skip_rest = false; - let mut symbols_exhausted = true; - let mut horizontal_offset = self.horizontal_offset as usize; - for StyledGrapheme { symbol, style } in &mut self.symbols { - symbols_exhausted = false; - - // Ignore characters wider that the total max width. - if Cast::<u16>::cast(symbol.width()) > self.max_line_width - { - continue; - } - - // Break on newline and discard it. - if symbol == "\n" { - break; - } - - if current_line_width + Cast::<u16>::cast(symbol.width()) - > self.max_line_width - { - // Exhaust the remainder of the line. - skip_rest = true; - break; - } - - let symbol = if horizontal_offset == 0 { - symbol - } else { - let w = symbol.width(); - if w > horizontal_offset { - let t = trim_offset(symbol, horizontal_offset); - horizontal_offset = 0; - t - } else { - horizontal_offset -= w; - "" - } - }; - current_line_width += Cast::<u16>::cast(symbol.width()); - self.current_line.push(StyledGrapheme { symbol, style }); - } - - if skip_rest { - for StyledGrapheme { symbol, .. } in &mut self.symbols { - if symbol == "\n" { - break; - } - } - } - - if symbols_exhausted && self.current_line.is_empty() { - None - } else { - Some((&self.current_line[..], current_line_width)) - } - } + fn next_line(&mut self) -> Option<(&[StyledGrapheme<'a>], u16)> { + if self.max_line_width == 0 { + return None; + } + + self.current_line.truncate(0); + let mut current_line_width = 0; + + let mut skip_rest = false; + let mut symbols_exhausted = true; + let mut horizontal_offset = self.horizontal_offset as usize; + for StyledGrapheme { symbol, style } in &mut self.symbols { + symbols_exhausted = false; + + // Ignore characters wider that the total max width. + if Cast::<u16>::cast(symbol.width()) > self.max_line_width + { + continue; + } + + // Break on newline and discard it. + if symbol == "\n" { + break; + } + + if current_line_width + Cast::<u16>::cast(symbol.width()) + > self.max_line_width + { + // Exhaust the remainder of the line. + skip_rest = true; + break; + } + + let symbol = if horizontal_offset == 0 { + symbol + } else { + let w = symbol.width(); + if w > horizontal_offset { + let t = trim_offset(symbol, horizontal_offset); + horizontal_offset = 0; + t + } else { + horizontal_offset -= w; + "" + } + }; + current_line_width += Cast::<u16>::cast(symbol.width()); + self.current_line.push(StyledGrapheme { symbol, style }); + } + + if skip_rest { + for StyledGrapheme { symbol, .. } in &mut self.symbols { + if symbol == "\n" { + break; + } + } + } + + if symbols_exhausted && self.current_line.is_empty() { + None + } else { + Some((&self.current_line[..], current_line_width)) + } + } } /// This function will return a str slice which start at specified offset. /// As src is a unicode str, start offset has to be calculated with each character. fn trim_offset(src: &str, mut offset: usize) -> &str { - let mut start = 0; - for c in UnicodeSegmentation::graphemes(src, true) { - let w = c.width(); - if w <= offset { - offset -= w; - start += c.len(); - } else { - break; - } - } - &src[start..] + let mut start = 0; + for c in UnicodeSegmentation::graphemes(src, true) { + let w = c.width(); + if w <= offset { + offset -= w; + start += c.len(); + } else { + break; + } + } + &src[start..] } #[cfg(test)] mod test { - use super::*; - use unicode_segmentation::UnicodeSegmentation; - - enum Composer { - WordWrapper { trim: bool }, - LineTruncator, - } - - fn run_composer( - which: Composer, - text: &str, - text_area_width: u16, - ) -> (Vec<String>, Vec<u16>) { - let style = Default::default(); - let mut styled = UnicodeSegmentation::graphemes(text, true) - .map(|g| StyledGrapheme { symbol: g, style }); - let mut composer: Box<dyn LineComposer> = match which { - 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![]; - let mut widths = vec![]; - while let Some((styled, width)) = composer.next_line() { - let line = styled - .iter() - .map(|StyledGrapheme { symbol, .. }| *symbol) - .collect::<String>(); - assert!(width <= text_area_width); - lines.push(line); - widths.push(width); - } - (lines, widths) - } - - #[test] - fn line_composer_one_line() { - let width = 40; - for i in 1..width { - let text = "a".repeat(i); - 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); - assert_eq!(line_truncator, expected); - } - } - - #[test] - fn line_composer_short_lines() { - let width = 20; - let text = + use super::*; + use unicode_segmentation::UnicodeSegmentation; + + enum Composer { + WordWrapper { trim: bool }, + LineTruncator, + } + + fn run_composer( + which: Composer, + text: &str, + text_area_width: u16, + ) -> (Vec<String>, Vec<u16>) { + let style = Default::default(); + let mut styled = UnicodeSegmentation::graphemes(text, true) + .map(|g| StyledGrapheme { symbol: g, style }); + let mut composer: Box<dyn LineComposer> = match which { + 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![]; + let mut widths = vec![]; + while let Some((styled, width)) = composer.next_line() { + let line = styled + .iter() + .map(|StyledGrapheme { symbol, .. }| *symbol) + .collect::<String>(); + assert!(width <= text_area_width); + lines.push(line); + widths.push(width); + } + (lines, widths) + } + + #[test] + fn line_composer_one_line() { + let width = 40; + for i in 1..width { + let text = "a".repeat(i); + 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); + assert_eq!(line_truncator, expected); + } + } + + #[test] + fn line_composer_short_lines() { + let width = 20; + let text = "abcdefg\nhijklmno\npabcdefg\nhijklmn\nopabcdefghijk\nlmnopabcd\n\n\nefghijklmno"; - 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(); - assert_eq!(word_wrapper, wrapped); - assert_eq!(line_truncator, wrapped); - } - - #[test] - fn line_composer_long_word() { - let width = 20; - let text = "abcdefghijklmnopabcdefghijklmnopabcdefghijklmnopabcdefghijklmno"; - 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![ - &text[..width], - &text[width..width * 2], - &text[width * 2..width * 3], - &text[width * 3..], - ]; - assert_eq!( + 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(); + assert_eq!(word_wrapper, wrapped); + assert_eq!(line_truncator, wrapped); + } + + #[test] + fn line_composer_long_word() { + let width = 20; + let text = "abcdefghijklmnopabcdefghijklmnopabcdefghijklmnopabcdefghijklmno"; + 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![ + &text[..width], + &text[width..width * 2], + &text[width * 2..width * 3], + &text[width * 3..], + ]; + assert_eq!( word_wrapper, wrapped, "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]]); - } + assert_eq!(line_truncator, vec![&text[..width]]); + } - #[test] - fn line_composer_long_sentence() { - let width = 20; - let text = + #[test] + fn line_composer_long_sentence() { + let width = 20; + let text = "abcd efghij klmnopabcd efgh ijklmnopabcdefg hijkl mnopab c d e f g h i j k l m n o"; - let text_multi_space = + let text_multi_space = "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 { 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![ - "abcd efghij", - "klmnopabcd efgh", - "ijklmnopabcdefg", - "hijkl mnopab c d e f", - "g h i j k l m n o", - ]; - assert_eq!(word_wrapper_single_space, word_wrapped); - assert_eq!(word_wrapper_multi_space, word_wrapped); - - assert_eq!(line_truncator, vec![&text[..width]]); - } - - #[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 { trim: true }, - text, - width, - ); - let (line_truncator, _) = - run_composer(Composer::LineTruncator, text, width); - - let expected: Vec<&str> = Vec::new(); - assert_eq!(word_wrapper, expected); - assert_eq!(line_truncator, expected); - } - - #[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 { trim: true }, - text, - width, - ); - let (line_truncator, _) = - run_composer(Composer::LineTruncator, text, width); - - let expected: Vec<&str> = - UnicodeSegmentation::graphemes(text, true) - .filter(|g| g.chars().any(|c| !c.is_whitespace())) - .collect(); - assert_eq!(word_wrapper, expected); - assert_eq!(line_truncator, vec!["a"]); - } - - #[test] - fn line_composer_max_line_width_of_1_double_width_characters() { - let width = 1; - let text = "コンピュータ上で文字を扱う場合、典型的には文字\naaaによる通信を行う場合にその\ + let (word_wrapper_single_space, _) = 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![ + "abcd efghij", + "klmnopabcd efgh", + "ijklmnopabcdefg", + "hijkl mnopab c d e f", + "g h i j k l m n o", + ]; + assert_eq!(word_wrapper_single_space, word_wrapped); + assert_eq!(word_wrapper_multi_space, word_wrapped); + + assert_eq!(line_truncator, vec![&text[..width]]); + } + + #[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 { trim: true }, + text, + width, + ); + let (line_truncator, _) = + run_composer(Composer::LineTruncator, text, width); + + let expected: Vec<&str> = Vec::new(); + assert_eq!(word_wrapper, expected); + assert_eq!(line_truncator, expected); + } + + #[test] + fn line_composer_max_line_width_of_1() { + let width = 1; |