Skip to content
This repository has been archived by the owner on Aug 6, 2023. It is now read-only.

Commit

Permalink
Paragraph: word wrapping
Browse files Browse the repository at this point in the history
  • Loading branch information
karolinepauls authored and fdehau committed Dec 22, 2018
1 parent 063ab2f commit 10642d0
Show file tree
Hide file tree
Showing 5 changed files with 555 additions and 116 deletions.
19 changes: 12 additions & 7 deletions examples/paragraph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@ fn main() -> Result<(), failure::Error> {

let events = Events::new();

let mut scroll: u16 = 0;
loop {
terminal.draw(|mut f| {
let size = f.size();

// Words made "loooong" to demonstrate line breaking.
let s = "Veeeeeeeeeeeeeeeery loooooooooooooooooong striiiiiiiiiiiiiiiiiiiiiiiiiing. ";
let s = "Veeeeeeeeeeeeeeeery loooooooooooooooooong striiiiiiiiiiiiiiiiiiiiiiiiiing. ";
let mut long_line = s.repeat(usize::from(size.width) / s.len() + 4);
long_line.push('\n');

Expand All @@ -58,16 +59,16 @@ fn main() -> Result<(), failure::Error> {
.split(size);

let text = [
Text::raw("This a line\n"),
Text::styled("This a line\n", Style::default().fg(Color::Red)),
Text::styled("This a line\n", Style::default().bg(Color::Blue)),
Text::raw("This is a line \n"),
Text::styled("This is a line \n", Style::default().fg(Color::Red)),
Text::styled("This is a line\n", Style::default().bg(Color::Blue)),
Text::styled(
"This a longer line\n",
"This is a longer line\n",
Style::default().modifier(Modifier::CrossedOut),
),
Text::raw(&long_line),
Text::styled(&long_line, Style::default().bg(Color::Green)),
Text::styled(
"This a line\n",
"This is a line\n",
Style::default().fg(Color::Green).modifier(Modifier::Italic),
),
];
Expand All @@ -88,6 +89,7 @@ fn main() -> Result<(), failure::Error> {
.block(block.clone().title("Center, wrap"))
.alignment(Alignment::Center)
.wrap(true)
.scroll(scroll)
.render(&mut f, chunks[2]);
Paragraph::new(text.iter())
.block(block.clone().title("Right, wrap"))
Expand All @@ -96,6 +98,9 @@ fn main() -> Result<(), failure::Error> {
.render(&mut f, chunks[3]);
})?;

scroll += 1;
scroll %= 10;

match events.next()? {
Event::Input(key) => {
if key == Key::Char('q') {
Expand Down
1 change: 1 addition & 0 deletions src/widgets/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod chart;
mod gauge;
mod list;
mod paragraph;
mod reflow;
mod sparkline;
mod table;
mod tabs;
Expand Down
103 changes: 27 additions & 76 deletions src/widgets/paragraph.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
use either::Either;
use itertools::{multipeek, MultiPeek};
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;

use buffer::Buffer;
use layout::{Alignment, Rect};
use style::Style;
use widgets::reflow::{LineComposer, LineTruncator, Styled, WordWrapper};
use widgets::{Block, Text, Widget};

fn get_line_offset(line_width: u16, text_area_width: u16, alignment: Alignment) -> u16 {
match alignment {
Alignment::Center => (text_area_width / 2).saturating_sub(line_width / 2),
Alignment::Right => text_area_width.saturating_sub(line_width),
Alignment::Left => 0,
}
}

/// A widget to display some text.
///
/// # Examples
Expand Down Expand Up @@ -96,7 +104,7 @@ where
}
}

impl<'a, 't, T> Widget for Paragraph<'a, 't, T>
impl<'a, 't, 'b, T> Widget for Paragraph<'a, 't, T>
where
T: Iterator<Item = &'t Text<'t>>,
{
Expand All @@ -116,94 +124,37 @@ where
self.background(&text_area, buf, self.style.bg);

let style = self.style;
let styled = self.text.by_ref().flat_map(|t| match *t {
let mut styled = self.text.by_ref().flat_map(|t| match *t {
Text::Raw(ref d) => {
let data: &'t str = d; // coerce to &str
Either::Left(UnicodeSegmentation::graphemes(data, true).map(|g| (g, style)))
Either::Left(UnicodeSegmentation::graphemes(data, true).map(|g| Styled(g, style)))
}
Text::Styled(ref d, s) => {
let data: &'t str = d; // coerce to &str
Either::Right(UnicodeSegmentation::graphemes(data, true).map(move |g| (g, s)))
Either::Right(UnicodeSegmentation::graphemes(data, true).map(move |g| Styled(g, s)))
}
});
let mut styled = multipeek(styled);

fn get_cur_line_len<'a, I: Iterator<Item = (&'a str, Style)>>(
styled: &mut MultiPeek<I>,
) -> u16 {
let mut line_len = 0;
while match &styled.peek() {
Some(&(x, _)) => x != "\n",
None => false,
} {
line_len += 1;
}
line_len
};

let mut x = match self.alignment {
Alignment::Center => {
(text_area.width / 2).saturating_sub(get_cur_line_len(&mut styled) / 2)
}
Alignment::Right => (text_area.width).saturating_sub(get_cur_line_len(&mut styled)),
Alignment::Left => 0,
let mut line_composer: Box<dyn LineComposer> = if self.wrapping {
Box::new(WordWrapper::new(&mut styled, text_area.width))
} else {
Box::new(LineTruncator::new(&mut styled, text_area.width))
};
let mut y = 0;

let mut remove_leading_whitespaces = false;
while let Some((string, style)) = styled.next() {
if string == "\n" {
x = match self.alignment {
Alignment::Center => {
(text_area.width / 2).saturating_sub(get_cur_line_len(&mut styled) / 2)
}

Alignment::Right => {
(text_area.width).saturating_sub(get_cur_line_len(&mut styled))
}
Alignment::Left => 0,
};
y += 1;
continue;
}
let token_end_index = x + string.width() as u16 - 1;
let last_column_index = text_area.width - 1;
if token_end_index > last_column_index {
if !self.wrapping {
continue; // Truncate the remainder of the line.
} else {
x = match self.alignment {
Alignment::Center => {
(text_area.width / 2).saturating_sub(get_cur_line_len(&mut styled) / 2)
}

Alignment::Right => {
(text_area.width).saturating_sub(get_cur_line_len(&mut styled) + 1)
}
Alignment::Left => 0,
};
y += 1;
remove_leading_whitespaces = true
while let Some((current_line, current_line_width)) = line_composer.next_line() {
if y >= self.scroll {
let mut x = get_line_offset(current_line_width, text_area.width, self.alignment);
for Styled(symbol, style) in current_line {
buf.get_mut(text_area.left() + x, text_area.top() + y - self.scroll)
.set_symbol(symbol)
.set_style(*style);
x += symbol.width() as u16;
}
}

if remove_leading_whitespaces && string == " " {
continue;
}
remove_leading_whitespaces = false;

if y > text_area.height + self.scroll - 1 {
y += 1;
if y >= text_area.height + self.scroll {
break;
}

if y < self.scroll {
continue;
}

buf.get_mut(text_area.left() + x, text_area.top() + y - self.scroll)
.set_symbol(string)
.set_style(style);
x += string.width() as u16;
}
}
}
Loading

0 comments on commit 10642d0

Please sign in to comment.