use embedded_io::Error as _;
use crate::error::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Key {
Char(char),
Enter,
Backspace,
Delete,
Tab,
Up,
Down,
Left,
Right,
Home,
End,
CtrlC,
CtrlU,
CtrlW,
CtrlL,
}
pub(crate) async fn read_byte<R>(reader: &mut R, pending: &mut Option<u8>) -> Result<Option<u8>>
where
R: embedded_io_async::Read,
{
if let Some(b) = pending.take() {
return Ok(Some(b));
}
let mut buf = [0u8; 1];
match reader.read(&mut buf).await {
Ok(1) => Ok(Some(buf[0])),
Ok(_) => Ok(None),
Err(e) => Err(Error::Io(e.kind())),
}
}
pub(crate) async fn read_key<R>(reader: &mut R, pending: &mut Option<u8>) -> Result<Option<Key>>
where
R: embedded_io_async::Read,
{
let Some(b) = read_byte(reader, pending).await? else {
return Ok(None);
};
let key = match b {
b'\r' | b'\n' => Key::Enter,
0x08 | 0x7F => Key::Backspace,
b'\t' => Key::Tab,
0x03 => Key::CtrlC,
0x15 => Key::CtrlU,
0x17 => Key::CtrlW,
0x0C => Key::CtrlL,
0x1B => {
let Some(b'[') = read_byte(reader, pending).await? else {
return Ok(None);
};
let Some(b3) = read_byte(reader, pending).await? else {
return Ok(None);
};
match b3 {
b'A' => Key::Up,
b'B' => Key::Down,
b'C' => Key::Right,
b'D' => Key::Left,
b'H' => Key::Home,
b'F' => Key::End,
b'3' => {
match read_byte(reader, pending).await? {
Some(b'~') => Key::Delete,
_ => return Ok(None),
}
}
_ => return Ok(None),
}
}
0x20..=0x7E => Key::Char(b as char),
#[cfg(feature = "unicode")]
0xC2..=0xDF => match read_utf8_char(reader, pending, b, 2).await? {
Some(c) => Key::Char(c),
None => return Ok(None),
},
#[cfg(feature = "unicode")]
0xE0..=0xEF => match read_utf8_char(reader, pending, b, 3).await? {
Some(c) => Key::Char(c),
None => return Ok(None),
},
#[cfg(feature = "unicode")]
0xF0..=0xF7 => match read_utf8_char(reader, pending, b, 4).await? {
Some(c) => Key::Char(c),
None => return Ok(None),
},
#[cfg(not(feature = "unicode"))]
0x80..=0xFF => Key::Char(b as char),
_ => return Ok(None),
};
Ok(Some(key))
}
#[cfg(feature = "unicode")]
async fn read_utf8_char<R>(
reader: &mut R,
pending: &mut Option<u8>,
lead: u8,
len: usize,
) -> Result<Option<char>>
where
R: embedded_io_async::Read,
{
let mut buf = [0u8; 4];
buf[0] = lead;
for slot in buf[1..len].iter_mut() {
match read_byte(reader, pending).await? {
Some(b) => *slot = b,
None => return Ok(None),
}
}
Ok(core::str::from_utf8(&buf[..len])
.ok()
.and_then(|s| s.chars().next()))
}