use console::{Key, Term};
use std::io::{self, Read, Write};
use super::cursor::StringCursor;
pub enum State<T> {
Active,
Submit(T),
Cancel,
Error(String),
}
#[derive(PartialEq, Eq)]
pub enum Event {
Key(Key),
}
fn wrap(text: &str, width: usize) -> String {
use textwrap::{core::Word, fill, Options, WordSeparator};
fill(
text,
Options::new(width).word_separator(
WordSeparator::Custom(|line| Box::new(vec![Word::from(line)].into_iter())),
),
)
}
pub trait PromptInteraction<T> {
fn render(&mut self, state: &State<T>) -> String;
fn on(&mut self, event: &Event) -> State<T>;
fn input(&mut self) -> Option<&mut StringCursor> {
None
}
fn allow_word_editing(&self) -> bool {
true
}
fn interact(&mut self) -> io::Result<T> {
self.interact_on(&mut Term::stderr())
}
fn interact_on(&mut self, term: &mut Term) -> io::Result<T> {
if !term.is_term() {
return Err(io::ErrorKind::NotConnected.into());
}
term.hide_cursor()?;
let result = self.interact_on_prepared(term);
term.show_cursor()?;
result
}
fn interact_on_prepared(&mut self, term: &mut Term) -> io::Result<T> {
let mut state = State::Active;
let mut prev_frame = String::new();
loop {
let frame = self.render(&state);
if frame != prev_frame {
let prev_frame_check = wrap(&prev_frame, term.size().1 as usize);
term.clear_last_lines(prev_frame_check.lines().count())?;
term.write_all(frame.as_bytes())?;
term.flush()?;
prev_frame = frame;
}
match state {
State::Submit(result) => return Ok(result),
State::Cancel => return Err(io::ErrorKind::Interrupted.into()),
_ => {}
}
match term.read_key() {
Ok(Key::Escape) => {
state = State::Cancel;
if let State::Cancel = self.on(&Event::Key(Key::Escape)) {
state = State::Active;
}
}
Ok(key) => {
let key = match key {
Key::Char('\x01') => Key::Home, Key::Char('\x05') => Key::End, Key::Char('\x10') => Key::ArrowUp, Key::Char('\x0e') => Key::ArrowDown, _ => key,
};
let word_editing = self.allow_word_editing();
if let Some(cursor) = self.input() {
match key {
Key::Char(chr) if !chr.is_ascii_control() => cursor.insert(chr),
Key::Backspace => cursor.delete_left(),
Key::Del => cursor.delete_right(),
Key::ArrowLeft => cursor.move_left(),
Key::ArrowRight => cursor.move_right(),
Key::ArrowUp => cursor.move_up(),
Key::ArrowDown => cursor.move_down(),
Key::Home => cursor.move_home(),
Key::End => cursor.move_end(),
Key::Char('\u{17}') if word_editing => cursor.delete_word_to_the_left(),
Key::UnknownEscSeq(ref chars) if word_editing => {
match chars.as_slice() {
['\u{7f}'] => cursor.delete_word_to_the_left(),
['b'] => cursor.move_left_by_word(),
['f'] => cursor.move_right_by_word(),
['[', '1', ';'] => {
let mut two_chars = [0; 2];
term.read_exact(&mut two_chars)?;
match two_chars {
[b'3', b'D'] | [b'5', b'D'] => {
cursor.move_left_by_word()
}
[b'3', b'C'] | [b'5', b'C'] => {
cursor.move_right_by_word()
}
_ => {}
}
}
_ => {}
}
}
_ => {}
}
}
state = self.on(&Event::Key(key));
}
Err(e) if e.kind() == io::ErrorKind::Interrupted => state = State::Cancel,
Err(e) => return Err(e),
}
}
}
}