keyfork-prompt: use raw mode for input

This commit is contained in:
Ryan Heywood 2025-01-30 12:10:36 -05:00
parent c232828290
commit 35e0eb57a0
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
3 changed files with 70 additions and 22 deletions

View File

@ -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<dyn std::error::Error>> {
let mut handler = default_handler().unwrap();
let transport_validator = mnemonic::MnemonicSetValidator {
word_lengths: [24],
};
let mut handler = default_handler()?;
let mnemonics = prompt_validated_wordlist::<English, _>(
&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(())
}

View File

@ -217,9 +217,9 @@ pub fn default_handler() -> Result<Box<dyn PromptHandler>, 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));

View File

@ -178,12 +178,14 @@ where
W: Write + AsRawFd + Sized,
{
fn prompt_input(&mut self, prompt: &str) -> Result<String> {
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(