use crate::event::Event;
use crate::parser::Parser;
use std::collections::VecDeque;
use std::io;
use std::sync::mpsc::{Receiver, RecvTimeoutError, TryRecvError};
use std::time::{Duration, Instant};
pub(crate) const ESCAPE_WAIT: Duration = Duration::from_millis(25);
pub(crate) const MAX_TIMEOUT: Duration = Duration::from_secs(24 * 60 * 60);
pub(crate) struct Input {
chunks: Receiver<Vec<u8>>,
parser: Parser,
pending_since: Option<Instant>,
queue: VecDeque<Event>,
}
impl Input {
pub(crate) fn new(chunks: Receiver<Vec<u8>>) -> Input {
Input {
chunks,
parser: Parser::new(),
pending_since: None,
queue: VecDeque::new(),
}
}
pub(crate) fn request_size(&mut self, timeout: Duration) -> Option<(u32, u32)> {
self.parser.expect_cursor_position();
let deadline = Instant::now() + timeout.min(MAX_TIMEOUT);
let size = loop {
while let Some(event) = self.parser.next_event() {
self.queue.push_back(event);
}
if let Some((row, column)) = self.parser.take_cursor_position() {
break Some((column, row));
}
let remaining = deadline.saturating_duration_since(Instant::now());
if remaining.is_zero() {
break None;
}
match self.chunks.recv_timeout(remaining) {
Ok(bytes) => {
self.parser.feed(&bytes);
self.pending_since = None;
}
Err(_) => break None,
}
};
self.parser.cancel_cursor_position();
size
}
pub(crate) fn request_key_release_support(&mut self, timeout: Duration) -> bool {
self.parser.take_keyboard_flags();
self.parser.take_attributes_reply();
let deadline = Instant::now() + timeout.min(MAX_TIMEOUT);
let mut flags_seen = false;
loop {
while let Some(event) = self.parser.next_event() {
self.queue.push_back(event);
}
flags_seen |= self.parser.take_keyboard_flags().is_some();
if self.parser.take_attributes_reply() {
return flags_seen;
}
let remaining = deadline.saturating_duration_since(Instant::now());
if remaining.is_zero() {
return false;
}
match self.chunks.recv_timeout(remaining) {
Ok(bytes) => {
self.parser.feed(&bytes);
self.pending_since = None;
}
Err(_) => return false,
}
}
}
pub(crate) fn poll(&mut self, timeout: Duration) -> io::Result<Option<Event>> {
if let Some(event) = self.queue.pop_front() {
return Ok(Some(event));
}
let deadline = Instant::now() + timeout.min(MAX_TIMEOUT);
loop {
let mut closed = false;
loop {
match self.chunks.try_recv() {
Ok(bytes) => {
self.parser.feed(&bytes);
self.pending_since = None;
}
Err(TryRecvError::Empty) => break,
Err(TryRecvError::Disconnected) => {
closed = true;
break;
}
}
}
if let Some(event) = self.parser.next_event() {
return Ok(Some(event));
}
if closed {
return match self.parser.flush() {
Some(event) => Ok(Some(event)),
None => Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"terminal input closed (stdin ended)",
)),
};
}
let now = Instant::now();
let mut wait = deadline.saturating_duration_since(now);
if self.parser.has_pending() {
let settle_at = *self.pending_since.get_or_insert(now) + ESCAPE_WAIT;
if now >= settle_at {
self.pending_since = None;
if let Some(event) = self.parser.flush() {
return Ok(Some(event));
}
} else {
wait = wait.min(settle_at - now);
}
} else {
self.pending_since = None;
}
match self.chunks.recv_timeout(wait) {
Ok(bytes) => {
self.parser.feed(&bytes);
self.pending_since = None;
}
Err(RecvTimeoutError::Timeout) => {
if Instant::now() >= deadline {
return Ok(None);
}
}
Err(RecvTimeoutError::Disconnected) => {}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::event::{Key, KeyCode, MouseButton, MouseKind};
use std::sync::mpsc::{channel, Sender};
use std::thread::sleep;
fn input() -> (Sender<Vec<u8>>, Input) {
let (tx, rx) = channel();
(tx, Input::new(rx))
}
fn key(code: KeyCode) -> Event {
Event::Key(Key::new(code))
}
const SHORT: Duration = Duration::from_millis(5);
const LONG: Duration = Duration::from_secs(2);
#[test]
fn returns_none_when_nothing_arrives_before_the_timeout() {
let (_tx, mut input) = input();
let start = Instant::now();
assert_eq!(input.poll(Duration::from_millis(30)).unwrap(), None);
assert!(start.elapsed() >= Duration::from_millis(30));
}
#[test]
fn returns_an_event_as_soon_as_its_bytes_arrive() {
let (tx, mut input) = input();
tx.send(b"a".to_vec()).unwrap();
let start = Instant::now();
assert_eq!(input.poll(LONG).unwrap(), Some(key(KeyCode::Char('a'))));
assert!(start.elapsed() < Duration::from_millis(500));
}
#[test]
fn several_events_in_one_chunk_come_out_one_per_poll_in_order() {
let (tx, mut input) = input();
tx.send(b"ab\x1b[A".to_vec()).unwrap();
assert_eq!(input.poll(LONG).unwrap(), Some(key(KeyCode::Char('a'))));
assert_eq!(input.poll(SHORT).unwrap(), Some(key(KeyCode::Char('b'))));
assert_eq!(input.poll(SHORT).unwrap(), Some(key(KeyCode::Up)));
assert_eq!(input.poll(SHORT).unwrap(), None);
}
#[test]
fn a_sequence_split_across_chunks_is_joined() {
let (tx, mut input) = input();
tx.send(b"\x1b[<0;3".to_vec()).unwrap();
tx.send(b";4M".to_vec()).unwrap();
match input.poll(LONG).unwrap() {
Some(Event::Mouse(m)) => {
assert_eq!(m.kind, MouseKind::Down(MouseButton::Left));
assert_eq!((m.x, m.y), (2, 3));
}
other => panic!("expected a mouse event, got {other:?}"),
}
}
#[test]
fn a_lone_escape_becomes_the_escape_key_after_the_wait() {
let (tx, mut input) = input();
tx.send(b"\x1b".to_vec()).unwrap();
let start = Instant::now();
assert_eq!(input.poll(LONG).unwrap(), Some(key(KeyCode::Esc)));
let waited = start.elapsed();
assert!(waited >= ESCAPE_WAIT, "settled too early: {waited:?}");
assert!(
waited < Duration::from_secs(1),
"waited too long: {waited:?}"
);
}
#[test]
fn escape_followed_by_the_rest_of_a_sequence_is_not_the_escape_key() {
let (tx, mut input) = input();
tx.send(b"\x1b".to_vec()).unwrap();
assert_eq!(input.poll(Duration::from_millis(1)).unwrap(), None);
tx.send(b"[A".to_vec()).unwrap();
assert_eq!(input.poll(LONG).unwrap(), Some(key(KeyCode::Up)));
}
#[test]
fn the_escape_wait_carries_over_between_short_polls() {
let (tx, mut input) = input();
tx.send(b"\x1b".to_vec()).unwrap();
assert_eq!(input.poll(Duration::ZERO).unwrap(), None);
sleep(ESCAPE_WAIT * 3);
assert_eq!(input.poll(Duration::ZERO).unwrap(), Some(key(KeyCode::Esc)));
}
#[test]
fn request_size_returns_width_and_height_from_the_reply() {
let (tx, mut input) = input();
tx.send(b"\x1b[24;80R".to_vec()).unwrap();
assert_eq!(input.request_size(LONG), Some((80, 24)));
}
#[test]
fn request_size_waits_for_a_reply_that_arrives_later() {
let (tx, mut input) = input();
let sender = std::thread::spawn(move || {
sleep(Duration::from_millis(40));
tx.send(b"\x1b[30;100R".to_vec()).unwrap();
tx
});
assert_eq!(input.request_size(LONG), Some((100, 30)));
drop(sender.join().unwrap());
}
#[test]
fn request_size_gives_up_after_the_timeout() {
let (_tx, mut input) = input();
let start = Instant::now();
assert_eq!(input.request_size(Duration::from_millis(30)), None);
assert!(start.elapsed() >= Duration::from_millis(30));
}
#[test]
fn keys_pressed_while_waiting_for_the_size_are_not_lost() {
let (tx, mut input) = input();
tx.send(b"ab\x1b[24;80Rc".to_vec()).unwrap();
assert_eq!(input.request_size(LONG), Some((80, 24)));
assert_eq!(input.poll(SHORT).unwrap(), Some(key(KeyCode::Char('a'))));
assert_eq!(input.poll(SHORT).unwrap(), Some(key(KeyCode::Char('b'))));
assert_eq!(input.poll(SHORT).unwrap(), Some(key(KeyCode::Char('c'))));
assert_eq!(input.poll(SHORT).unwrap(), None);
}
#[test]
fn after_a_timed_out_request_a_lookalike_key_is_a_key_again() {
let (tx, mut input) = input();
assert_eq!(input.request_size(Duration::from_millis(10)), None);
tx.send(b"\x1b[1;5R".to_vec()).unwrap();
match input.poll(LONG).unwrap() {
Some(Event::Key(k)) => assert_eq!(k.code, KeyCode::F(3)),
other => panic!("expected Ctrl+F3, got {other:?}"),
}
}
#[test]
fn request_size_on_closed_input_returns_none() {
let (tx, mut input) = input();
drop(tx);
assert_eq!(input.request_size(LONG), None);
}
#[test]
fn an_enormous_timeout_does_not_overflow_or_panic() {
let (tx, mut input) = input();
tx.send(b"a".to_vec()).unwrap();
assert_eq!(
input.poll(Duration::MAX).unwrap(),
Some(key(KeyCode::Char('a')))
);
}
#[test]
fn polling_with_a_zero_timeout_does_not_block() {
let (_tx, mut input) = input();
let start = Instant::now();
assert_eq!(input.poll(Duration::ZERO).unwrap(), None);
assert!(start.elapsed() < Duration::from_millis(200));
}
#[test]
fn closed_input_still_delivers_what_was_sent_then_reports_the_end() {
let (tx, mut input) = input();
tx.send(b"a".to_vec()).unwrap();
drop(tx);
assert_eq!(input.poll(LONG).unwrap(), Some(key(KeyCode::Char('a'))));
let error = input.poll(LONG).unwrap_err();
assert_eq!(error.kind(), io::ErrorKind::UnexpectedEof);
}
#[test]
fn a_pending_escape_is_delivered_when_the_input_closes() {
let (tx, mut input) = input();
tx.send(b"\x1b".to_vec()).unwrap();
drop(tx);
assert_eq!(input.poll(LONG).unwrap(), Some(key(KeyCode::Esc)));
assert!(input.poll(LONG).is_err());
}
const KEY_QUERY_REPLY: &[u8] = b"\x1b[?11u";
const ATTRIBUTES_REPLY: &[u8] = b"\x1b[?62;22c";
#[test]
fn key_release_is_supported_when_both_replies_come_in_one_chunk() {
let (tx, mut input) = input();
tx.send([KEY_QUERY_REPLY, ATTRIBUTES_REPLY].concat())
.unwrap();
assert!(input.request_key_release_support(LONG));
}
#[test]
fn key_release_is_not_supported_when_only_the_attributes_reply_comes() {
let (tx, mut input) = input();
tx.send(ATTRIBUTES_REPLY.to_vec()).unwrap();
assert!(!input.request_key_release_support(LONG));
}
#[test]
fn key_release_is_not_supported_when_neither_reply_comes_in_time() {
let (_tx, mut input) = input();
let start = Instant::now();
assert!(!input.request_key_release_support(Duration::from_millis(50)));
assert!(start.elapsed() >= Duration::from_millis(50));
}
#[test]
fn key_release_support_is_the_same_wherever_the_replies_are_split() {
let both = [KEY_QUERY_REPLY, ATTRIBUTES_REPLY].concat();
for split in 1..both.len() {
let (tx, mut input) = input();
tx.send(both[..split].to_vec()).unwrap();
tx.send(both[split..].to_vec()).unwrap();
assert!(input.request_key_release_support(LONG), "split at {split}");
}
for split in 1..ATTRIBUTES_REPLY.len() {
let (tx, mut input) = input();
tx.send(ATTRIBUTES_REPLY[..split].to_vec()).unwrap();
tx.send(ATTRIBUTES_REPLY[split..].to_vec()).unwrap();
assert!(!input.request_key_release_support(LONG), "split at {split}");
}
}
#[test]
fn a_key_typed_while_waiting_is_kept_for_poll_and_does_not_break_detection() {
let (tx, mut input) = input();
tx.send(b"a".to_vec()).unwrap();
tx.send([KEY_QUERY_REPLY, ATTRIBUTES_REPLY].concat())
.unwrap();
assert!(input.request_key_release_support(LONG));
assert_eq!(input.poll(SHORT).unwrap(), Some(key(KeyCode::Char('a'))));
assert_eq!(input.poll(SHORT).unwrap(), None);
}
#[test]
fn reply_bytes_never_come_out_of_poll_as_events() {
let (tx, mut input) = input();
tx.send([KEY_QUERY_REPLY, ATTRIBUTES_REPLY].concat())
.unwrap();
assert!(input.request_key_release_support(LONG));
assert_eq!(input.poll(SHORT).unwrap(), None);
}
#[test]
fn a_stale_reply_from_before_the_request_does_not_count() {
let (tx, mut input) = input();
tx.send(KEY_QUERY_REPLY.to_vec()).unwrap();
assert_eq!(input.poll(SHORT).unwrap(), None);
tx.send(ATTRIBUTES_REPLY.to_vec()).unwrap();
assert!(!input.request_key_release_support(LONG));
}
}