use std::time::Duration;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
enum State {
Idle,
Esc(KeyEvent),
Intro(KeyEvent),
}
pub struct Repair {
state: State,
}
impl Default for Repair {
fn default() -> Self {
Self { state: State::Idle }
}
}
impl Repair {
pub const TIMEOUT: Duration = Duration::from_millis(15);
pub fn pending(&self) -> bool {
!matches!(self.state, State::Idle)
}
pub fn push(&mut self, key: KeyEvent) -> Vec<KeyEvent> {
let plain = key.modifiers.is_empty();
match std::mem::replace(&mut self.state, State::Idle) {
State::Idle => {
if plain && key.code == KeyCode::Esc {
self.state = State::Esc(key);
return Vec::new();
}
vec![key]
}
State::Esc(esc) => {
if plain && matches!(key.code, KeyCode::Char('[') | KeyCode::Char('O')) {
self.state = State::Intro(esc);
return Vec::new();
}
if plain && key.code == KeyCode::Esc {
self.state = State::Esc(key);
return vec![esc];
}
vec![esc, key]
}
State::Intro(esc) => {
if plain && let Some(code) = final_byte(key.code) {
return vec![KeyEvent::new(code, KeyModifiers::NONE)];
}
vec![
esc,
KeyEvent::new(KeyCode::Char('['), KeyModifiers::NONE),
key,
]
}
}
}
pub fn flush(&mut self) -> Vec<KeyEvent> {
match std::mem::replace(&mut self.state, State::Idle) {
State::Idle => Vec::new(),
State::Esc(esc) => vec![esc],
State::Intro(esc) => vec![esc, KeyEvent::new(KeyCode::Char('['), KeyModifiers::NONE)],
}
}
}
fn final_byte(code: KeyCode) -> Option<KeyCode> {
match code {
KeyCode::Char('A') => Some(KeyCode::Up),
KeyCode::Char('B') => Some(KeyCode::Down),
KeyCode::Char('C') => Some(KeyCode::Right),
KeyCode::Char('D') => Some(KeyCode::Left),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn press(code: KeyCode) -> KeyEvent {
KeyEvent::new(code, KeyModifiers::NONE)
}
fn codes(keys: Vec<KeyEvent>) -> Vec<KeyCode> {
keys.into_iter().map(|k| k.code).collect()
}
#[test]
fn ordinary_keys_pass_straight_through() {
let mut r = Repair::default();
for code in [KeyCode::Char('j'), KeyCode::Down, KeyCode::Enter] {
assert_eq!(codes(r.push(press(code))), vec![code]);
assert!(!r.pending());
}
}
#[test]
fn split_csi_and_ss3_sequences_become_cursor_keys() {
for intro in ['[', 'O'] {
for (final_byte, want) in [
('A', KeyCode::Up),
('B', KeyCode::Down),
('C', KeyCode::Right),
('D', KeyCode::Left),
] {
let mut r = Repair::default();
assert!(r.push(press(KeyCode::Esc)).is_empty());
assert!(r.pending(), "esc is held while the tail is awaited");
assert!(r.push(press(KeyCode::Char(intro))).is_empty());
assert_eq!(codes(r.push(press(KeyCode::Char(final_byte)))), vec![want]);
assert!(!r.pending());
}
}
}
#[test]
fn a_burst_of_split_scrolls_never_yields_an_esc() {
let mut r = Repair::default();
let mut out = Vec::new();
for _ in 0..50 {
for code in [KeyCode::Esc, KeyCode::Char('['), KeyCode::Char('B')] {
out.extend(codes(r.push(press(code))));
}
}
assert_eq!(out, vec![KeyCode::Down; 50]);
assert!(!r.pending());
}
#[test]
fn a_real_esc_press_survives_the_timeout_flush() {
let mut r = Repair::default();
assert!(r.push(press(KeyCode::Esc)).is_empty());
assert_eq!(codes(r.flush()), vec![KeyCode::Esc]);
assert!(!r.pending());
assert!(r.flush().is_empty(), "flushing twice is a no-op");
}
#[test]
fn esc_followed_by_another_key_releases_both_in_order() {
let mut r = Repair::default();
assert!(r.push(press(KeyCode::Esc)).is_empty());
assert_eq!(
codes(r.push(press(KeyCode::Char('j')))),
vec![KeyCode::Esc, KeyCode::Char('j')]
);
assert!(!r.pending());
}
#[test]
fn repeated_esc_presses_all_get_through() {
let mut r = Repair::default();
assert!(r.push(press(KeyCode::Esc)).is_empty());
assert_eq!(codes(r.push(press(KeyCode::Esc))), vec![KeyCode::Esc]);
assert_eq!(codes(r.flush()), vec![KeyCode::Esc]);
}
#[test]
fn an_unfinished_sequence_replays_what_it_swallowed() {
let mut r = Repair::default();
assert!(r.push(press(KeyCode::Esc)).is_empty());
assert!(r.push(press(KeyCode::Char('['))).is_empty());
assert_eq!(
codes(r.push(press(KeyCode::Char('x')))),
vec![KeyCode::Esc, KeyCode::Char('['), KeyCode::Char('x')]
);
let mut r = Repair::default();
assert!(r.push(press(KeyCode::Esc)).is_empty());
assert!(r.push(press(KeyCode::Char('['))).is_empty());
assert_eq!(codes(r.flush()), vec![KeyCode::Esc, KeyCode::Char('[')]);
}
#[test]
fn modified_keys_are_never_mistaken_for_a_sequence() {
let mut r = Repair::default();
let ctrl_esc = KeyEvent::new(KeyCode::Esc, KeyModifiers::CONTROL);
assert_eq!(codes(r.push(ctrl_esc)), vec![KeyCode::Esc]);
assert!(!r.pending());
assert!(r.push(press(KeyCode::Esc)).is_empty());
let alt_bracket = KeyEvent::new(KeyCode::Char('['), KeyModifiers::ALT);
assert_eq!(
codes(r.push(alt_bracket)),
vec![KeyCode::Esc, KeyCode::Char('[')]
);
}
}