Optionally break word and trim trailing white spaces for WordWrapper
Phoenix-Chen opened this issue · comments
Problem
Current implementation of WordWrapper trims trailing white spaces by default and does not allow break word. However, in some situations, I would like to keep the trailing white spaces. See screenshot below:
Solution
My solution to this problem is add break_word: bool
to WordWrapper
and Wrap
. Then in WordWrapper.next_line
check break_word
and truncate/extend characters accordingly.
I have implemented above approach at this branch.
Alternatively, I can also make break_word
default to false
, that would make less impact to current code.
I'll provide a sample script (based on examples/paragraph.rs
) you can test with in the comments. (you might need to adjust your terminal size a bit)
Please let me know if this is a feature you would like to include. If so, I'll write some test cases and open an PR.
Here is the sample script you can test with:
use crossterm::{
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use std::{
error::Error,
io,
time::{Duration, Instant},
};
use tui::{
backend::{Backend, CrosstermBackend},
layout::{Alignment, Constraint, Direction, Layout},
style::{Color, Modifier, Style},
text::{Span, Spans},
widgets::{Block, Borders, Paragraph, Wrap},
Frame, Terminal,
};
struct App {
scroll: u16,
}
impl App {
fn new() -> App {
App { scroll: 0 }
}
fn on_tick(&mut self) {
self.scroll += 1;
self.scroll %= 10;
}
}
fn main() -> Result<(), Box<dyn Error>> {
// setup terminal
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
// create app and run it
let tick_rate = Duration::from_millis(250);
let app = App::new();
let res = run_app(&mut terminal, app, tick_rate);
// restore terminal
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
terminal.show_cursor()?;
if let Err(err) = res {
println!("{:?}", err)
}
Ok(())
}
fn run_app<B: Backend>(
terminal: &mut Terminal<B>,
mut app: App,
tick_rate: Duration,
) -> io::Result<()> {
let mut last_tick = Instant::now();
loop {
terminal.draw(|f| ui(f, &app))?;
let timeout = tick_rate
.checked_sub(last_tick.elapsed())
.unwrap_or_else(|| Duration::from_secs(0));
if crossterm::event::poll(timeout)? {
if let Event::Key(key) = event::read()? {
if let KeyCode::Char('q') = key.code {
return Ok(());
}
}
}
if last_tick.elapsed() >= tick_rate {
app.on_tick();
last_tick = Instant::now();
}
}
}
fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
let size = f.size();
let block = Block::default().style(Style::default().bg(Color::White).fg(Color::Black));
f.render_widget(block, size);
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(1)
.constraints(
[
Constraint::Percentage(50),
Constraint::Percentage(50),
]
.as_ref(),
)
.split(size);
let text = vec![
// Spans::from("This is a line "),
Spans::from(
vec![
Span::styled(
" Current WordWrapper implementation trims trailing spaces by default. See following spaces with black background -> ",
Style::default().fg(Color::Red),
),
Span::styled(
" ",
Style::default().bg(Color::Black),
),
Span::styled(
" or blue ",
Style::default().fg(Color::Red),
),
Span::styled(
" ",
Style::default().bg(Color::Blue),
),
Span::styled(
" or yellow ",
Style::default().fg(Color::Red),
),
Span::styled(
" ",
Style::default().bg(Color::Yellow),
)
]
),
Spans::from(
vec![
Span::styled(
"Current WordWrapper does not break word. ",
Style::default().fg(Color::Red),
),
Span::styled(
"Supercalifragilisticexpialidocious",
Style::default().fg(Color::Black),
),
]
),
];
let create_block = |title| {
Block::default()
.borders(Borders::ALL)
.style(Style::default().bg(Color::White).fg(Color::Black))
.title(Span::styled(
title,
Style::default().add_modifier(Modifier::BOLD),
))
};
let paragraph = Paragraph::new(text.clone())
.block(create_block("Current implementation"))
.wrap(Wrap { trim: false, break_word: false });
f.render_widget(paragraph, chunks[0]);
let paragraph = Paragraph::new(text)
.block(create_block("Implement break_word"))
.wrap(Wrap { trim: false, break_word: true });
f.render_widget(paragraph, chunks[1]);
}