use tokio::io::AsyncReadExt as _;
use crate::private::Input as _;
pub struct RawGuard {
termios: Option<rustix::termios::Termios>,
}
impl RawGuard {
#[allow(clippy::missing_panics_doc)]
pub async fn new() -> crate::error::Result<Self> {
let termios = tokio::task::spawn_blocking(move || {
rustix::termios::tcgetattr(std::io::stdin())
.map_err(crate::error::Error::SetTerminalMode)
})
.await
.unwrap()?;
let mut termios_raw = termios.clone();
termios_raw.make_raw();
tokio::task::spawn_blocking(move || {
rustix::termios::tcsetattr(
std::io::stdin(),
rustix::termios::OptionalActions::Now,
&termios_raw,
)
.map_err(crate::error::Error::SetTerminalMode)
})
.await
.unwrap()?;
Ok(Self {
termios: Some(termios),
})
}
#[allow(clippy::missing_panics_doc)]
pub async fn cleanup(&mut self) -> crate::error::Result<()> {
if let Some(termios) = self.termios.take() {
tokio::task::spawn_blocking(move || {
rustix::termios::tcsetattr(
std::io::stdin(),
rustix::termios::OptionalActions::Now,
&termios,
)
.map_err(crate::error::Error::SetTerminalMode)
})
.await
.unwrap()
} else {
Ok(())
}
}
}
impl Drop for RawGuard {
fn drop(&mut self) {
if let Some(termios) = self.termios.take() {
let _ = rustix::termios::tcsetattr(
std::io::stdin(),
rustix::termios::OptionalActions::Now,
&termios,
);
}
}
}
pub struct Input {
stdin: tokio::io::Stdin,
raw: Option<RawGuard>,
buf: Vec<u8>,
pos: usize,
parse_utf8: bool,
parse_ctrl: bool,
parse_meta: bool,
parse_special_keys: bool,
parse_single: bool,
}
impl crate::private::Input for Input {
fn buf(&self) -> &[u8] {
&self.buf[self.pos..]
}
fn buf_mut(&mut self) -> &mut [u8] {
&mut self.buf[self.pos..]
}
fn buf_mut_vec(&mut self) -> &mut Vec<u8> {
&mut self.buf
}
fn consume(&mut self, n: usize) {
self.pos += n;
}
fn unconsume(&mut self, n: usize) {
self.pos -= n;
}
fn buf_is_empty(&self) -> bool {
self.pos >= self.buf.len()
}
fn buf_at_beginning(&self) -> bool {
self.pos == 0
}
fn should_parse_utf8(&self) -> bool {
self.parse_utf8
}
fn should_parse_ctrl(&self) -> bool {
self.parse_ctrl
}
fn should_parse_meta(&self) -> bool {
self.parse_meta
}
fn should_parse_special_keys(&self) -> bool {
self.parse_special_keys
}
}
impl Input {
pub async fn new() -> crate::error::Result<Self> {
let mut self_ = Self::new_without_raw();
self_.raw = Some(RawGuard::new().await?);
Ok(self_)
}
#[must_use]
pub fn new_without_raw() -> Self {
Self {
stdin: tokio::io::stdin(),
raw: None,
buf: Vec::with_capacity(4096),
pos: 0,
parse_utf8: true,
parse_ctrl: true,
parse_meta: true,
parse_special_keys: true,
parse_single: true,
}
}
pub fn take_raw_guard(&mut self) -> Option<RawGuard> {
self.raw.take()
}
pub fn parse_utf8(&mut self, parse: bool) {
self.parse_utf8 = parse;
}
pub fn parse_ctrl(&mut self, parse: bool) {
self.parse_ctrl = parse;
}
pub fn parse_meta(&mut self, parse: bool) {
self.parse_meta = parse;
}
pub fn parse_special_keys(&mut self, parse: bool) {
self.parse_special_keys = parse;
}
pub fn parse_single(&mut self, parse: bool) {
self.parse_single = parse;
}
pub async fn read_key(
&mut self,
) -> crate::error::Result<Option<crate::Key>> {
self.fill_buf().await?;
if self.parse_single {
Ok(self.read_single_key())
} else {
if let Some(key) = self.try_read_string() {
return Ok(Some(key));
}
if let Some(key) = self.try_read_bytes() {
return Ok(Some(key));
}
if let Some(key) = self.read_single_key() {
return Ok(Some(self.normalize_to_bytes(key)));
}
Ok(None)
}
}
async fn fill_buf(&mut self) -> crate::error::Result<()> {
if self.buf_is_empty() {
self.buf.resize(4096, 0);
self.pos = 0;
let bytes = read_stdin(&mut self.stdin, &mut self.buf).await?;
if bytes == 0 {
return Ok(());
}
self.buf.truncate(bytes);
}
if self.parse_utf8 {
let expected_bytes =
self.expected_leading_utf8_bytes(self.buf()[0]);
if self.buf.len() < self.pos + expected_bytes {
let mut cur = self.buf.len();
self.buf.resize(4096 + expected_bytes, 0);
while cur < self.pos + expected_bytes {
let bytes =
read_stdin(&mut self.stdin, &mut self.buf[cur..])
.await?;
if bytes == 0 {
return Ok(());
}
cur += bytes;
}
self.buf.truncate(cur);
}
}
Ok(())
}
}
async fn read_stdin(
stdin: &mut tokio::io::Stdin,
buf: &mut [u8],
) -> crate::error::Result<usize> {
stdin
.read(buf)
.await
.map_err(crate::error::Error::ReadStdin)
}