From 35e0eb57a0f1043e01a278011dd75b646ff663da Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 30 Jan 2025 12:10:36 -0500 Subject: [PATCH] keyfork-prompt: use raw mode for input --- .../examples/test-basic-prompt.rs | 19 ++---- crates/util/keyfork-prompt/src/lib.rs | 6 +- crates/util/keyfork-prompt/src/terminal.rs | 67 +++++++++++++++++-- 3 files changed, 70 insertions(+), 22 deletions(-) diff --git a/crates/util/keyfork-prompt/examples/test-basic-prompt.rs b/crates/util/keyfork-prompt/examples/test-basic-prompt.rs index 9fdce69..5aca0d2 100644 --- a/crates/util/keyfork-prompt/examples/test-basic-prompt.rs +++ b/crates/util/keyfork-prompt/examples/test-basic-prompt.rs @@ -1,26 +1,15 @@ #![allow(missing_docs)] use keyfork_prompt::{ - prompt_validated_wordlist, - validators::{mnemonic, Validator}, + Message, default_handler, }; -use keyfork_mnemonic::English; - fn main() -> Result<(), Box> { - let mut handler = default_handler().unwrap(); - let transport_validator = mnemonic::MnemonicSetValidator { - word_lengths: [24], - }; + let mut handler = default_handler()?; - let mnemonics = prompt_validated_wordlist::( - &mut *handler, - "Enter a 24-word mnemonic: ", - 3, - &*transport_validator.to_fn(), - )?; - assert_eq!(mnemonics[0].as_bytes().len(), 32); + let output = handler.prompt_input("Test message: ")?; + handler.prompt_message(Message::Text(format!("Result: {output}")))?; Ok(()) } diff --git a/crates/util/keyfork-prompt/src/lib.rs b/crates/util/keyfork-prompt/src/lib.rs index 3fc7e46..149b1c8 100644 --- a/crates/util/keyfork-prompt/src/lib.rs +++ b/crates/util/keyfork-prompt/src/lib.rs @@ -217,9 +217,9 @@ pub fn default_handler() -> Result, DefaultHandlerError> } } - // stdout can be not-a-terminal and we'll just override it anyways, stdin is the - // important one. - if std::io::stdin().is_terminal() { + // we can revert stdin to a readable input by using raw mode, but we can't do the more + // significant operations if we don't have access to a terminal stderr + if std::io::stderr().is_terminal() { // because this is a "guessed" handler, let's take the nice route and not error, just skip. if let Ok(terminal) = default_terminal() { return Ok(Box::new(terminal)); diff --git a/crates/util/keyfork-prompt/src/terminal.rs b/crates/util/keyfork-prompt/src/terminal.rs index fdfd388..0326cf7 100644 --- a/crates/util/keyfork-prompt/src/terminal.rs +++ b/crates/util/keyfork-prompt/src/terminal.rs @@ -178,12 +178,14 @@ where W: Write + AsRawFd + Sized, { fn prompt_input(&mut self, prompt: &str) -> Result { - let mut terminal = self.lock().alternate_screen()?; + let mut terminal = self.lock().alternate_screen()?.raw_mode()?; terminal .queue(terminal::Clear(terminal::ClearType::All))? .queue(cursor::MoveTo(0, 0))?; let mut lines = prompt.lines().peekable(); + let mut prefix_length = 0; while let Some(line) = lines.next() { + prefix_length = line.len(); terminal.queue(Print(line))?; if lines.peek().is_some() { terminal @@ -193,9 +195,66 @@ where } terminal.flush()?; - let mut line = String::new(); - terminal.read.read_line(&mut line)?; - Ok(line) + let (mut cols, mut _rows) = terminal.size()?; + + let mut input = String::new(); + loop { + let input_len = input.len(); + match read()? { + Event::Resize(new_cols, new_rows) => { + cols = new_cols; + _rows = new_rows; + } + Event::Key(k) => match k.code { + KeyCode::Enter => { + break; + } + KeyCode::Backspace => { + if input.pop().is_some() && prefix_length + input_len < cols as usize { + terminal + .queue(cursor::MoveLeft(1))? + .queue(Print(" "))? + .queue(cursor::MoveLeft(1))? + .flush()?; + } + } + KeyCode::Char('w') if k.modifiers.contains(KeyModifiers::CONTROL) => { + let mut has_deleted_text = true; + while input.pop().is_some_and(char::is_whitespace) { + has_deleted_text = false; + } + while input.pop().is_some_and(|c| !c.is_whitespace()) { + has_deleted_text = true; + } + if !input.is_empty() && has_deleted_text { + input.push(' '); + } + } + KeyCode::Char('c') if k.modifiers.contains(KeyModifiers::CONTROL) => { + return Err(Error::CtrlC); + } + KeyCode::Char(c) => { + input.push(c); + } + _ => (), + }, + _ => (), + } + + let printable_start = if prefix_length + input.len() < cols as usize { + 0 + } else { + let printable_space = (cols as usize) - prefix_length; + input.len() - (printable_space - 1) + }; + terminal + .queue(cursor::MoveToColumn(prefix_length as u16))? + .queue(terminal::Clear(terminal::ClearType::UntilNewLine))? + .queue(Print(&input[printable_start..]))? + .flush()?; + } + + Ok(input) } fn prompt_validated_wordlist(