Skip to content

Commit

Permalink
Merge pull request #218 from mgeisler/interactive-demo
Browse files Browse the repository at this point in the history
Add interactive word-wrapping demo
  • Loading branch information
mgeisler committed Nov 1, 2020
2 parents c708b78 + d31b5f6 commit fe5b491
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 0 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
This file lists the most important changes made in each release of
`textwrap`.

## Unreleased

* Added a new interactive demo, which demonstrates the features of
`textwrap`. Clone the repository and run `cargo run --example
interactive` to try it (Linux only).

## Version 0.12.1 — July 3rd, 2020

This is a bugfix release.
Expand Down
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,6 @@ hyphenation = { version = "0.8", optional = true, features = ["embed_en-us"] }
lipsum = "0.7"
version-sync = "0.9"
criterion = "0.3"

[target.'cfg(unix)'.dev-dependencies]
termion = "1.5"
209 changes: 209 additions & 0 deletions examples/interactive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
// The example only works on Linux since Termion does not yet support
// Windows: https://gitlab.redox-os.org/redox-os/termion/-/issues/103
// The precise library doesn't matter much, so feel free to send a PR
// if there is a library with good Windows support.

fn main() -> Result<(), std::io::Error> {
#[cfg(not(unix))]
panic!("Sorry, this example currently only works on Unix!");

#[cfg(unix)]
unix_only::main()
}

#[cfg(unix)]
mod unix_only {
use std::io::{self, Write};
use termion::event::Key;
use termion::input::TermRead;
use termion::raw::{IntoRawMode, RawTerminal};
use termion::screen::AlternateScreen;
use termion::{color, cursor, style};
use textwrap::{wrap, HyphenSplitter, NoHyphenation, Options, WordSplitter};

#[cfg(feature = "hyphenation")]
use hyphenation::{Language, Load, Standard};

fn draw_margins(
row: u16,
col: u16,
line_width: u16,
left: char,
right: char,
stdout: &mut RawTerminal<io::Stdout>,
) -> Result<(), io::Error> {
write!(
stdout,
"{}{}{}{}",
cursor::Goto(col - 1, row),
color::Fg(color::Red),
left,
color::Fg(color::Reset),
)?;
write!(
stdout,
"{}{}{}{}",
cursor::Goto(col + line_width, row),
color::Fg(color::Red),
right,
color::Fg(color::Reset),
)?;

Ok(())
}

fn draw_text<'a>(
text: &str,
options: &Options<'a>,
splitter_label: &str,
stdout: &mut RawTerminal<io::Stdout>,
) -> Result<(), io::Error> {
let mut row: u16 = 1;
let col: u16 = 3;

write!(stdout, "{}", termion::clear::All)?;
write!(
stdout,
"{}{}Settings:{}",
cursor::Goto(col, row),
style::Bold,
style::Reset,
)?;
row += 1;

write!(
stdout,
"{}- width: {}{}{} (use ← and → to change)",
cursor::Goto(col, row),
style::Bold,
options.width,
style::Reset,
)?;
row += 1;

write!(
stdout,
"{}- break_words: {}{:?}{} (toggle with Ctrl-b)",
cursor::Goto(col, row),
style::Bold,
options.break_words,
style::Reset,
)?;
row += 1;

write!(
stdout,
"{}- splitter: {}{}{} (cycle with Ctrl-s)",
cursor::Goto(col, row),
style::Bold,
splitter_label,
style::Reset,
)?;
row += 2;

let mut lines = wrap(text, options).collect::<Vec<_>>();
if let Some(line) = lines.last() {
// If `text` ends with a newline, the final wrapped line
// contains this newline. This will in turn leave the
// cursor hanging in the middle of the line. Pushing an
// extra empty line fixes this.
if line.ends_with('\n') {
lines.push("".into());
}
} else {
// No lines -> we add an empty line so we have a place
// where we can display the cursor.
lines.push("".into());
}

// Draw margins extended one line above and below the wrapped
// text. This serves to indicate the margins if `break_words`
// is `false` and `width` is very small.
draw_margins(row, col, options.width as u16, '┌', '┐', stdout)?;
let final_row = row + lines.len() as u16 + 1;
draw_margins(final_row, col, options.width as u16, '└', '┘', stdout)?;
row += 1;

for line in lines {
draw_margins(row, col, options.width as u16, '│', '│', stdout)?;
write!(stdout, "{}{}", cursor::Goto(col, row), line)?;
row += 1;
}

stdout.flush()
}

pub fn main() -> Result<(), io::Error> {
let initial_width = 20;

let mut labels = vec![
String::from("HyphenSplitter"),
String::from("NoHyphenation"),
];

let mut splitters: Vec<Box<dyn WordSplitter>> =
vec![Box::new(HyphenSplitter), Box::new(NoHyphenation)];

// If you like, you can download more dictionaries from
// https://github.com/tapeinosyne/hyphenation/tree/master/dictionaries
// Place the dictionaries in the examples/ directory. Here we
// just load the embedded en-us dictionary.
#[cfg(feature = "hyphenation")]
for lang in &[Language::EnglishUS] {
let dictionary = Standard::from_embedded(*lang).or_else(|_| {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("examples")
.join(format!("{}.standard.bincode", lang.code()));
Standard::from_path(*lang, &path)
});

if let Ok(dict) = dictionary {
labels.push(format!("{} hyphenation", lang.code()));
splitters.push(Box::new(dict));
}
}

let mut label = labels.pop().unwrap();
let mut options = Options::new(initial_width);
options.break_words = false;
options.splitter = splitters.pop().unwrap();

let mut idx_iter = (0..splitters.len()).collect::<Vec<_>>().into_iter().cycle();

let mut text = String::from(
"Welcome to the interactive word-wrapping demo! Use the arrow \
keys to change the line length and try typing your own text!",
);

let stdin = io::stdin();
let mut screen = AlternateScreen::from(io::stdout().into_raw_mode()?);
write!(screen, "{}", cursor::BlinkingUnderline)?;
draw_text(&text, &options, &label, &mut screen)?;

for c in stdin.keys() {
match c? {
Key::Esc | Key::Ctrl('c') => break,
Key::Left => options.width = options.width.saturating_sub(1),
Key::Right => options.width = options.width.saturating_add(1),
Key::Ctrl('b') => options.break_words = !options.break_words,
Key::Ctrl('s') => {
let idx = idx_iter.next().unwrap();
std::mem::swap(&mut options.splitter, &mut splitters[idx]);
std::mem::swap(&mut label, &mut labels[idx]);
}
Key::Char(c) => text.push(c),
Key::Backspace => {
text.pop();
}
_ => {}
}

draw_text(&text, &options, &label, &mut screen)?;
}

// TODO: change to cursor::DefaultStyle if
// https://github.com/redox-os/termion/pull/157 is merged.
screen.write(b"\x1b[0 q")?;
screen.flush()
}
}

0 comments on commit fe5b491

Please sign in to comment.