Compare commits

...

6 Commits

Author SHA1 Message Date
Leon Sautour
f32fb022e0 docs(example): add break word behaviour to paragraph example 2023-04-26 21:14:41 +02:00
charliedu2000
12e4f0d921 Feature: allow word-break in wrapped paragraphs, add it to document. 2022-08-13 00:07:29 +08:00
charliedu2000
727e13d876 Feature: allow word-break in wrapped paragraphs. 2022-08-12 23:49:24 +08:00
charliedu2000
71835a0a52 Feature: allow word-break in wrapped paragraphs. 2022-08-12 23:40:08 +08:00
♫ Christian Krause ♫
a6b25a4877 chore: add panic hook example (#593)
Without a terminal-resetting panic hook there are two main problems when
an application panics:

1.  The report of the panic is distorted because the terminal has not
    properly left the alternate screen and is still in raw mode.

2.  The terminal needs to be manually reset with the `reset` command.

To avoid this, the standard panic hook can be extended to first reset
the terminal.
2022-04-24 16:49:57 +02:00
Florian Dehau
90d8cb6526 chore: add more apps using tui to the README 2022-04-24 15:49:03 +02:00
9 changed files with 440 additions and 55 deletions

View File

@@ -64,6 +64,10 @@ required-features = ["crossterm"]
name = "list"
required-features = ["crossterm"]
[[example]]
name = "panic"
required-features = ["crossterm"]
[[example]]
name = "paragraph"
required-features = ["crossterm"]

View File

@@ -108,6 +108,13 @@ You can run all examples by running `cargo make run-examples` (require
* [joshuto](https://github.com/kamiyaa/joshuto)
* [adsb_deku/radar](https://github.com/wcampbell0x2a/adsb_deku#radar-tui)
* [hoard](https://github.com/Hyde46/hoard)
* [tokio-console](https://github.com/tokio-rs/console): a diagnostics and debugging tool for asynchronous Rust programs.
* [hwatch](https://github.com/blacknon/hwatch): a alternative watch command that records the result of command execution and can display its history and diffs.
* [ytui-music](https://github.com/sudipghimire533/ytui-music): listen to music from youtube inside your terminal.
* [mqttui](https://github.com/EdJoPaTo/mqttui): subscribe or publish to a MQTT Topic quickly from the terminal.
* [meteo-tui](https://github.com/16arpi/meteo-tui): french weather via the command line.
* [picterm](https://github.com/ksk001100/picterm): preview images in your terminal.
* [gobang](https://github.com/TaKO8Ki/gobang): a cross-platform TUI database management tool.
### Alternatives

View File

@@ -293,7 +293,10 @@ where
.fg(Color::Magenta)
.add_modifier(Modifier::BOLD),
));
let paragraph = Paragraph::new(text).block(block).wrap(Wrap { trim: true });
let paragraph = Paragraph::new(text).block(block).wrap(Wrap {
trim: true,
break_words: false,
});
f.render_widget(paragraph, area);
}

142
examples/panic.rs Normal file
View File

@@ -0,0 +1,142 @@
//! How to use a panic hook to reset the terminal before printing the panic to
//! the terminal.
//!
//! When exiting normally or when handling `Result::Err`, we can reset the
//! terminal manually at the end of `main` just before we print the error.
//!
//! Because a panic interrupts the normal control flow, manually resetting the
//! terminal at the end of `main` won't do us any good. Instead, we need to
//! make sure to set up a panic hook that first resets the terminal before
//! handling the panic. This both reuses the standard panic hook to ensure a
//! consistent panic handling UX and properly resets the terminal to not
//! distort the output.
//!
//! That's why this example is set up to show both situations, with and without
//! the chained panic hook, to see the difference.
#![deny(clippy::all)]
#![warn(clippy::pedantic, clippy::nursery)]
use std::error::Error;
use std::io;
use crossterm::event::{self, Event, KeyCode};
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
use crossterm::terminal::{EnterAlternateScreen, LeaveAlternateScreen};
use tui::backend::{Backend, CrosstermBackend};
use tui::layout::Alignment;
use tui::text::Spans;
use tui::widgets::{Block, Borders, Paragraph};
use tui::{Frame, Terminal};
type Result<T> = std::result::Result<T, Box<dyn Error>>;
#[derive(Default)]
struct App {
hook_enabled: bool,
}
impl App {
fn chain_hook(&mut self) {
let original_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |panic| {
reset_terminal().unwrap();
original_hook(panic);
}));
self.hook_enabled = true;
}
}
fn main() -> Result<()> {
let mut terminal = init_terminal()?;
let mut app = App::default();
let res = run_tui(&mut terminal, &mut app);
reset_terminal()?;
if let Err(err) = res {
println!("{:?}", err);
}
Ok(())
}
/// Initializes the terminal.
fn init_terminal() -> Result<Terminal<CrosstermBackend<io::Stdout>>> {
crossterm::execute!(io::stdout(), EnterAlternateScreen)?;
enable_raw_mode()?;
let backend = CrosstermBackend::new(io::stdout());
let mut terminal = Terminal::new(backend)?;
terminal.hide_cursor()?;
Ok(terminal)
}
/// Resets the terminal.
fn reset_terminal() -> Result<()> {
disable_raw_mode()?;
crossterm::execute!(io::stdout(), LeaveAlternateScreen)?;
Ok(())
}
/// Runs the TUI loop.
fn run_tui<B: Backend>(terminal: &mut Terminal<B>, app: &mut App) -> io::Result<()> {
loop {
terminal.draw(|f| ui(f, app))?;
if let Event::Key(key) = event::read()? {
match key.code {
KeyCode::Char('p') => {
panic!("intentional demo panic");
}
KeyCode::Char('e') => {
app.chain_hook();
}
_ => {
return Ok(());
}
}
}
}
}
/// Render the TUI.
fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
let text = vec![
if app.hook_enabled {
Spans::from("HOOK IS CURRENTLY **ENABLED**")
} else {
Spans::from("HOOK IS CURRENTLY **DISABLED**")
},
Spans::from(""),
Spans::from("press `p` to panic"),
Spans::from("press `e` to enable the terminal-resetting panic hook"),
Spans::from("press any other key to quit without panic"),
Spans::from(""),
Spans::from("when you panic without the chained hook,"),
Spans::from("you will likely have to reset your terminal afterwards"),
Spans::from("with the `reset` command"),
Spans::from(""),
Spans::from("with the chained panic hook enabled,"),
Spans::from("you should see the panic report as you would without tui"),
Spans::from(""),
Spans::from("try first without the panic handler to see the difference"),
];
let b = Block::default()
.title("Panic Handler Demo")
.borders(Borders::ALL);
let p = Paragraph::new(text).block(b).alignment(Alignment::Center);
f.render_widget(p, f.size());
}

View File

@@ -153,19 +153,28 @@ fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
.style(Style::default().bg(Color::White).fg(Color::Black))
.block(create_block("Left, wrap"))
.alignment(Alignment::Left)
.wrap(Wrap { trim: true });
.wrap(Wrap {
trim: true,
break_words: false,
});
f.render_widget(paragraph, chunks[1]);
let paragraph = Paragraph::new(text.clone())
.style(Style::default().bg(Color::White).fg(Color::Black))
.block(create_block("Center, wrap"))
.alignment(Alignment::Center)
.wrap(Wrap { trim: true })
.wrap(Wrap {
trim: true,
break_words: false,
})
.scroll((app.scroll, 0));
f.render_widget(paragraph, chunks[2]);
let paragraph = Paragraph::new(text)
.style(Style::default().bg(Color::White).fg(Color::Black))
.block(create_block("Right, wrap"))
.block(create_block("Right, wrap (break words)"))
.alignment(Alignment::Right)
.wrap(Wrap { trim: true });
.wrap(Wrap {
trim: true,
break_words: true,
});
f.render_widget(paragraph, chunks[3]);
}

View File

@@ -83,7 +83,10 @@ fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
Style::default().add_modifier(Modifier::SLOW_BLINK),
))
.alignment(Alignment::Center)
.wrap(Wrap { trim: true });
.wrap(Wrap {
trim: true,
break_words: false,
});
f.render_widget(paragraph, chunks[0]);
let block = Block::default()

View File

@@ -40,7 +40,7 @@ fn get_line_offset(line_width: u16, text_area_width: u16, alignment: Alignment)
/// .block(Block::default().title("Paragraph").borders(Borders::ALL))
/// .style(Style::default().fg(Color::White).bg(Color::Black))
/// .alignment(Alignment::Center)
/// .wrap(Wrap { trim: true });
/// .wrap(Wrap { trim: true, break_words: false });
/// ```
#[derive(Debug, Clone)]
pub struct Paragraph<'a> {
@@ -70,7 +70,7 @@ pub struct Paragraph<'a> {
/// - Here is another point that is long enough to wrap"#);
///
/// // With leading spaces trimmed (window width of 30 chars):
/// Paragraph::new(bullet_points.clone()).wrap(Wrap { trim: true });
/// Paragraph::new(bullet_points.clone()).wrap(Wrap { trim: true, break_words: false });
/// // Some indented points:
/// // - First thing goes here and is
/// // long so that it wraps
@@ -78,7 +78,7 @@ pub struct Paragraph<'a> {
/// // is long enough to wrap
///
/// // But without trimming, indentation is preserved:
/// Paragraph::new(bullet_points).wrap(Wrap { trim: false });
/// Paragraph::new(bullet_points).wrap(Wrap { trim: false, break_words: false });
/// // Some indented points:
/// // - First thing goes here
/// // and is long so that it wraps
@@ -89,6 +89,8 @@ pub struct Paragraph<'a> {
pub struct Wrap {
/// Should leading whitespace be trimmed
pub trim: bool,
/// Should words at the end of lines be broken
pub break_words: bool,
}
impl<'a> Paragraph<'a> {
@@ -162,15 +164,21 @@ impl<'a> Widget for Paragraph<'a> {
}))
});
let mut line_composer: Box<dyn LineComposer> = if let Some(Wrap { trim }) = self.wrap {
Box::new(WordWrapper::new(&mut styled, text_area.width, trim))
} else {
let mut line_composer = Box::new(LineTruncator::new(&mut styled, text_area.width));
if let Alignment::Left = self.alignment {
line_composer.set_horizontal_offset(self.scroll.1);
}
line_composer
};
let mut line_composer: Box<dyn LineComposer> =
if let Some(Wrap { trim, break_words }) = self.wrap {
Box::new(WordWrapper::new(
&mut styled,
text_area.width,
trim,
break_words,
))
} else {
let mut line_composer = Box::new(LineTruncator::new(&mut styled, text_area.width));
if let Alignment::Left = self.alignment {
line_composer.set_horizontal_offset(self.scroll.1);
}
line_composer
};
let mut y = 0;
while let Some((current_line, current_line_width)) = line_composer.next_line() {
if y >= self.scroll.0 {

View File

@@ -19,6 +19,7 @@ pub struct WordWrapper<'a, 'b> {
next_line: Vec<StyledGrapheme<'a>>,
/// Removes the leading whitespace from lines
trim: bool,
break_words: bool,
}
impl<'a, 'b> WordWrapper<'a, 'b> {
@@ -26,6 +27,7 @@ impl<'a, 'b> WordWrapper<'a, 'b> {
symbols: &'b mut dyn Iterator<Item = StyledGrapheme<'a>>,
max_line_width: u16,
trim: bool,
break_words: bool,
) -> WordWrapper<'a, 'b> {
WordWrapper {
symbols,
@@ -33,6 +35,7 @@ impl<'a, 'b> WordWrapper<'a, 'b> {
current_line: vec![],
next_line: vec![],
trim,
break_words,
}
}
}
@@ -87,11 +90,12 @@ impl<'a, 'b> LineComposer<'a> for WordWrapper<'a, 'b> {
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 {
(symbols_to_last_word_end, width_to_last_word_end)
} else {
(self.current_line.len() - 1, self.max_line_width)
};
let (truncate_at, truncated_width) =
if symbols_to_last_word_end != 0 && !self.break_words {
(symbols_to_last_word_end, width_to_last_word_end)
} else {
(self.current_line.len() - 1, self.max_line_width)
};
// Push the remainder to the next line but strip leading whitespace:
{
@@ -235,7 +239,7 @@ mod test {
use unicode_segmentation::UnicodeSegmentation;
enum Composer {
WordWrapper { trim: bool },
WordWrapper { trim: bool, break_words: bool },
LineTruncator,
}
@@ -244,9 +248,12 @@ mod test {
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::WordWrapper { trim, break_words } => Box::new(WordWrapper::new(
&mut styled,
text_area_width,
trim,
break_words,
)),
Composer::LineTruncator => Box::new(LineTruncator::new(&mut styled, text_area_width)),
};
let mut lines = vec![];
@@ -268,8 +275,14 @@ mod test {
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 (word_wrapper, _) = run_composer(
Composer::WordWrapper {
trim: true,
break_words: 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);
@@ -282,7 +295,14 @@ mod test {
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 (word_wrapper, _) = run_composer(
Composer::WordWrapper {
trim: true,
break_words: true,
},
text,
width,
);
let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
let wrapped: Vec<&str> = text.split('\n').collect();
@@ -294,8 +314,14 @@ mod 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 (word_wrapper, _) = run_composer(
Composer::WordWrapper {
trim: true,
break_words: true,
},
text,
width as u16,
);
let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width as u16);
let wrapped = vec![
@@ -320,10 +346,19 @@ mod test {
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_single_space, _) = run_composer(
Composer::WordWrapper {
trim: true,
break_words: true,
},
text,
width as u16,
);
let (word_wrapper_multi_space, _) = run_composer(
Composer::WordWrapper { trim: true },
Composer::WordWrapper {
trim: true,
break_words: true,
},
text_multi_space,
width as u16,
);
@@ -346,7 +381,14 @@ 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 { trim: true }, text, width);
let (word_wrapper, _) = run_composer(
Composer::WordWrapper {
trim: true,
break_words: true,
},
text,
width,
);
let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
let expected: Vec<&str> = Vec::new();
@@ -358,7 +400,14 @@ 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 { trim: true }, text, width);
let (word_wrapper, _) = run_composer(
Composer::WordWrapper {
trim: true,
break_words: true,
},
text,
width,
);
let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
let expected: Vec<&str> = UnicodeSegmentation::graphemes(text, true)
@@ -373,7 +422,14 @@ mod test {
let width = 1;
let text = "コンピュータ上で文字を扱う場合、典型的には文字\naaaによる通信を行う場合にその\
両端点では、";
let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
let (word_wrapper, _) = run_composer(
Composer::WordWrapper {
trim: true,
break_words: 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"]);
@@ -384,7 +440,14 @@ 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 { trim: true }, text, width);
let (word_wrapper, _) = run_composer(
Composer::WordWrapper {
trim: true,
break_words: true,
},
text,
width,
);
assert_eq!(
word_wrapper,
vec![
@@ -402,8 +465,14 @@ mod test {
let width = 20;
let text = "コンピュータ上で文字を扱う場合、典型的には文字による通信を行う場合にその両端点\
では、";
let (word_wrapper, word_wrapper_width) =
run_composer(Composer::WordWrapper { trim: true }, text, width);
let (word_wrapper, word_wrapper_width) = run_composer(
Composer::WordWrapper {
trim: true,
break_words: true,
},
text,
width,
);
let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
assert_eq!(line_truncator, vec!["コンピュータ上で文字"]);
let wrapped = vec![
@@ -421,7 +490,14 @@ mod test {
fn line_composer_leading_whitespace_removal() {
let width = 20;
let text = "AAAAAAAAAAAAAAAAAAAA AAA";
let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
let (word_wrapper, _) = run_composer(
Composer::WordWrapper {
trim: true,
break_words: 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"]);
@@ -432,7 +508,14 @@ mod test {
fn line_composer_lots_of_spaces() {
let width = 20;
let text = " ";
let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
let (word_wrapper, _) = run_composer(
Composer::WordWrapper {
trim: true,
break_words: true,
},
text,
width,
);
let (line_truncator, _) = run_composer(Composer::LineTruncator, text, width);
assert_eq!(word_wrapper, vec![""]);
assert_eq!(line_truncator, vec![" "]);
@@ -444,7 +527,14 @@ mod test {
fn line_composer_char_plus_lots_of_spaces() {
let width = 20;
let text = "a ";
let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width);
let (word_wrapper, _) = run_composer(
Composer::WordWrapper {
trim: true,
break_words: 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
@@ -463,8 +553,14 @@ 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 { trim: true }, text, width);
let (word_wrapper, word_wrapper_width) = run_composer(
Composer::WordWrapper {
trim: true,
break_words: true,
},
text,
width,
);
assert_eq!(
word_wrapper,
vec![
@@ -485,13 +581,26 @@ 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 { trim: true }, text, width);
let (word_wrapper, _) = run_composer(
Composer::WordWrapper {
trim: true,
break_words: 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 { trim: true }, &text_space, width);
let (word_wrapper_space, _) = run_composer(
Composer::WordWrapper {
trim: true,
break_words: true,
},
&text_space,
width,
);
assert_eq!(word_wrapper_space, vec!["AAAAAAAAAAAAAAA AAAA", "AAA",]);
}
@@ -499,7 +608,14 @@ mod 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);
let (word_wrapper, _) = run_composer(
Composer::WordWrapper {
trim: false,
break_words: true,
},
text,
width,
);
assert_eq!(word_wrapper, vec!["AAAAAAAAAAAAAAAAAAAA", " AAA",]);
}
@@ -507,7 +623,14 @@ mod 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);
let (word_wrapper, _) = run_composer(
Composer::WordWrapper {
trim: false,
break_words: true,
},
text,
width,
);
assert_eq!(
word_wrapper,
vec!["AAA AAA", "AAAAA AA", "AAAAAA", " B", " C", " D"]
@@ -518,7 +641,14 @@ mod 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);
let (word_wrapper, _) = run_composer(
Composer::WordWrapper {
trim: false,
break_words: true,
},
text,
width,
);
assert_eq!(
word_wrapper,
vec![

View File

@@ -25,7 +25,10 @@ fn widgets_paragraph_can_wrap_its_content() {
let paragraph = Paragraph::new(text)
.block(Block::default().borders(Borders::ALL))
.alignment(alignment)
.wrap(Wrap { trim: true });
.wrap(Wrap {
trim: true,
break_words: false,
});
f.render_widget(paragraph, size);
})
.unwrap();
@@ -79,6 +82,76 @@ fn widgets_paragraph_can_wrap_its_content() {
);
}
#[test]
fn widgets_paragraph_can_wrap_its_content_with_word_break_enabled() {
let test_case = |alignment, expected| {
let backend = TestBackend::new(20, 10);
let mut terminal = Terminal::new(backend).unwrap();
terminal
.draw(|f| {
let size = f.size();
let text = vec![Spans::from(SAMPLE_STRING)];
let paragraph = Paragraph::new(text)
.block(Block::default().borders(Borders::ALL))
.alignment(alignment)
.wrap(Wrap {
trim: true,
break_words: true,
});
f.render_widget(paragraph, size);
})
.unwrap();
terminal.backend().assert_buffer(&expected);
};
test_case(
Alignment::Left,
Buffer::with_lines(vec![
"┌──────────────────┐",
"│The library is bas│",
"│ed on the principl│",
"│e of immediate ren│",
"│dering with interm│",
"│ediate buffers. Th│",
"│is means that at e│",
"│ach new frame you │",
"│should build all w│",
"└──────────────────┘",
]),
);
test_case(
Alignment::Right,
Buffer::with_lines(vec![
"┌──────────────────┐",
"│The library is bas│",
"│ed on the principl│",
"│e of immediate ren│",
"│dering with interm│",
"│ediate buffers. Th│",
"│is means that at e│",
"│ach new frame you │",
"│should build all w│",
"└──────────────────┘",
]),
);
test_case(
Alignment::Center,
Buffer::with_lines(vec![
"┌──────────────────┐",
"│The library is bas│",
"│ed on the principl│",
"│e of immediate ren│",
"│dering with interm│",
"│ediate buffers. Th│",
"│is means that at e│",
"│ach new frame you │",
"│should build all w│",
"└──────────────────┘",
]),
);
}
#[test]
fn widgets_paragraph_renders_double_width_graphemes() {
let backend = TestBackend::new(10, 10);
@@ -91,7 +164,10 @@ fn widgets_paragraph_renders_double_width_graphemes() {
let text = vec![Spans::from(s)];
let paragraph = Paragraph::new(text)
.block(Block::default().borders(Borders::ALL))
.wrap(Wrap { trim: true });
.wrap(Wrap {
trim: true,
break_words: false,
});
f.render_widget(paragraph, size);
})
.unwrap();
@@ -123,7 +199,10 @@ fn widgets_paragraph_renders_mixed_width_graphemes() {
let text = vec![Spans::from(s)];
let paragraph = Paragraph::new(text)
.block(Block::default().borders(Borders::ALL))
.wrap(Wrap { trim: true });
.wrap(Wrap {
trim: true,
break_words: false,
});
f.render_widget(paragraph, size);
})
.unwrap();