use std::io::{self, stdout};
use crossterm::{
cursor::{position, MoveLeft, MoveToNextLine},
event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers},
execute,
style::Print,
terminal,
};
#[derive(thiserror::Error, Debug)]
#[error(transparent)]
pub struct Error {
inner: io::Error,
}
impl Error {
fn from_io(other: io::Error) -> Self {
Self {
inner: other,
}
}
}
pub fn try_scanpw(echo: Option<char>) -> Result<String, Error> {
terminal::enable_raw_mode().map_err(Error::from_io)?;
let (max_left, _height) = position().map_err(Error::from_io)?;
let mut pw = String::new();
loop {
if let Event::Key(k) = event::read().map_err(Error::from_io)? {
match k {
KeyEvent {
kind: KeyEventKind::Press | KeyEventKind::Repeat,
code: KeyCode::Enter,
..
} => {
execute!(stdout(), MoveToNextLine(1))
.map_err(Error::from_io)?;
break;
}
KeyEvent {
kind: KeyEventKind::Press | KeyEventKind::Repeat,
code: KeyCode::Backspace,
..
} => {
if echo.is_some() {
let (cur_left, _height) =
position().map_err(Error::from_io)?;
let not_too_far = cur_left
.checked_sub(1)
.map_or(false, |np| np >= max_left);
if not_too_far {
execute!(
stdout(),
MoveLeft(1),
Print(" "),
MoveLeft(1)
)
.map_err(Error::from_io)?;
}
}
pw.pop();
}
KeyEvent {
kind: KeyEventKind::Press | KeyEventKind::Repeat,
code: KeyCode::Char('c'),
modifiers,
..
} if modifiers == KeyModifiers::CONTROL => {
terminal::disable_raw_mode().map_err(Error::from_io)?;
ctrl_c_self();
}
KeyEvent {
kind: KeyEventKind::Press | KeyEventKind::Repeat,
code: KeyCode::Char(c),
..
} => {
if let Some(c) = echo {
execute!(stdout(), Print(c)).map_err(Error::from_io)?;
}
pw.push(c);
}
_ => (),
}
}
}
terminal::disable_raw_mode().map_err(Error::from_io)?;
Ok(pw)
}
macro_rules! ctrl_c_self_doc {
() => {
"Send the signal generated by `C-c` to the current process"
};
}
#[cfg(unix)]
#[doc = ctrl_c_self_doc!()]
fn ctrl_c_self() {
use nix::sys::signal::{raise, Signal::SIGINT};
assert!(
raise(SIGINT).is_ok(),
"failed to send SIGINT to the current process"
);
}
#[cfg(windows)]
#[doc = ctrl_c_self_doc!()]
fn ctrl_c_self() {
use windows::Win32::System::{
Console::{GenerateConsoleCtrlEvent, CTRL_C_EVENT},
Threading::GetCurrentProcessId,
};
let res = unsafe {
GenerateConsoleCtrlEvent(CTRL_C_EVENT, GetCurrentProcessId())
};
assert!(
res.ok().is_ok(),
"failed to send CTRL_C_EVENT to the current process"
);
}
#[cfg(not(any(unix, windows)))]
#[doc = ctrl_c_self_doc!()]
fn ctrl_c_self() {
std::process::exit(1);
}