Compare commits
6 Commits
v0.18.0
...
653/master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f32fb022e0 | ||
|
|
12e4f0d921 | ||
|
|
727e13d876 | ||
|
|
71835a0a52 | ||
|
|
a6b25a4877 | ||
|
|
90d8cb6526 |
@@ -64,6 +64,10 @@ required-features = ["crossterm"]
|
||||
name = "list"
|
||||
required-features = ["crossterm"]
|
||||
|
||||
[[example]]
|
||||
name = "panic"
|
||||
required-features = ["crossterm"]
|
||||
|
||||
[[example]]
|
||||
name = "paragraph"
|
||||
required-features = ["crossterm"]
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
142
examples/panic.rs
Normal 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());
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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![
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user