summaryrefslogtreecommitdiffstats
path: root/src/ui
diff options
context:
space:
mode:
authorStephan Dilly <dilly.stephan@gmail.com>2021-08-17 14:24:25 +0200
committerStephan Dilly <dilly.stephan@gmail.com>2021-08-17 14:24:25 +0200
commitdad8e8d43de79a3c39a37f118e529dc88fab5495 (patch)
tree32329a450106142d35374c88b776c74a679bd9a0 /src/ui
parent25a49e22f267f467fb7436c799e1d01294e0a914 (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.rs174
-rw-r--r--src/ui/reflow.rs1217
-rw-r--r--src/ui/scrollbar.rs110
-rw-r--r--src/ui/scrolllist.rs112
-rw-r--r--src/ui/stateful_paragraph.rs346
-rw-r--r--src/ui/style.rs556
-rw-r--r--src/ui/syntax_text.rs302
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;