use std::io::{self, Read};
use std::process::{Command, Stdio};
pub struct Raw {
original: String,
pendentes: Vec<u8>,
}
impl Raw {
pub fn ativar() -> Option<Self> {
let original = stty(&["-g"])?.trim().to_string();
if original.is_empty() {
return None;
}
stty(&["-echo", "-icanon", "-isig", "min", "1", "time", "0"])?;
Some(Self {
original,
pendentes: Vec::new(),
})
}
pub fn ler_tecla(&mut self) -> io::Result<Tecla> {
if self.pendentes.is_empty() {
let mut buf = [0u8; 16];
let n = io::stdin().read(&mut buf)?;
if n == 0 {
return Ok(Tecla::Sair); }
self.pendentes.extend_from_slice(&buf[..n]);
}
Ok(self.proxima())
}
fn proxima(&mut self) -> Tecla {
let p = &self.pendentes;
let (tecla, consumir) = match p.as_slice() {
[b'\r', ..] | [b'\n', ..] => (Tecla::Enter, 1),
[3, ..] | [26, ..] => (Tecla::Interromper, 1), [0x1b, b'[', b'A', ..] | [0x1b, b'O', b'A', ..] => (Tecla::Cima, 3),
[0x1b, b'[', b'B', ..] | [0x1b, b'O', b'B', ..] => (Tecla::Baixo, 3),
[0x1b, b'[', ..] => (Tecla::Outra, 3), [0x1b, ..] => (Tecla::Sair, p.len()), [b'q', ..] | [b'Q', ..] => (Tecla::Sair, 1),
[b'k', ..] => (Tecla::Cima, 1),
[b'j', ..] => (Tecla::Baixo, 1),
_ => (Tecla::Outra, 1),
};
self.pendentes.drain(..consumir.min(self.pendentes.len()));
tecla
}
}
impl Drop for Raw {
fn drop(&mut self) {
let _ = stty(&[self.original.as_str()]);
}
}
pub fn colunas() -> Option<usize> {
let saida = stty(&["size"])?;
saida.split_whitespace().nth(1)?.trim().parse().ok()
}
fn stty(args: &[&str]) -> Option<String> {
let saida = Command::new("stty")
.args(args)
.stdin(Stdio::inherit())
.stderr(Stdio::null())
.output()
.ok()?;
saida
.status
.success()
.then(|| String::from_utf8_lossy(&saida.stdout).into_owned())
}
pub enum Tecla {
Cima,
Baixo,
Enter,
Sair,
Interromper,
Outra,
}