use color_eyre::Report;
use color_eyre::eyre::eyre;
use crossterm::tty::IsTty;
use crossterm::{
event::{Event, EventStream, KeyCode, KeyEvent, KeyModifiers},
terminal::{disable_raw_mode, enable_raw_mode},
};
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::io;
use tokio_stream::Stream;
use tracing::info;
use tracing::instrument;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UserEvent {
Quit,
Kill,
}
impl TryFrom<KeyEvent> for UserEvent {
type Error = Report;
fn try_from(ke: KeyEvent) -> Result<Self, Self::Error> {
if ke.code == KeyCode::Char('q')
|| (ke.code == KeyCode::Char('d') && ke.modifiers == KeyModifiers::CONTROL)
{
Ok(UserEvent::Quit)
} else if ke.code == KeyCode::Char('k')
|| (ke.code == KeyCode::Char('c') && ke.modifiers == KeyModifiers::CONTROL)
{
Ok(UserEvent::Kill)
} else {
Err(eyre!("unrecognized key event {:?}", ke))
}
}
}
impl TryFrom<&Event> for UserEvent {
type Error = Report;
fn try_from(event: &Event) -> Result<Self, Self::Error> {
match event {
Event::Key(ke) => UserEvent::try_from(*ke),
event => Err(eyre!("unrecognized event {:?}", event)),
}
}
}
#[derive(Debug, Default)]
pub enum UserStream {
Real(EventStream),
#[default]
Virtual,
}
impl UserStream {
pub fn new_real() -> Option<UserStream> {
let stdin = io::stdin();
if stdin.is_tty() {
let _ = enable_raw_mode();
Some(UserStream::Real(EventStream::new()))
} else {
None
}
}
pub fn new_virtual() -> UserStream {
UserStream::Virtual
}
}
impl Drop for UserStream {
fn drop(&mut self) {
if matches!(self, UserStream::Real(_)) {
let _ = disable_raw_mode();
}
}
}
impl Stream for UserStream {
type Item = UserEvent;
#[instrument(level = "debug", ret, skip(cx))]
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
match this {
UserStream::Real(event_stream) => {
let next = Pin::new(event_stream).poll_next(cx);
match next {
Poll::Ready(Some(Ok(event))) => match UserEvent::try_from(&event) {
Ok(user_event) => Poll::Ready(Some(user_event)),
Err(e) => {
info!(
event = ?event,
error=%e,
"error converting Event into UserEvent"
);
Poll::Pending
}
},
Poll::Ready(Some(Err(error))) => {
info!(
error = %error,
"EventStream yielded an error"
);
Poll::Pending
}
Poll::Ready(None) => Poll::Ready(None),
Poll::Pending => Poll::Pending,
}
}
UserStream::Virtual => Poll::Ready(None),
}
}
}