use super::*;
fn pk(s: &str) -> Key {
parse_keypress(s.as_bytes())
}
fn kitty_key(
codepoint: u32,
modifiers: Option<u32>,
event_type: Option<u32>,
text_codepoints: Option<&[u32]>,
) -> String {
let mut seq = format!("\u{1b}[{codepoint}");
if modifiers.is_some() || event_type.is_some() || text_codepoints.is_some() {
seq += &format!(";{}", modifiers.unwrap_or(1));
}
if event_type.is_some() || text_codepoints.is_some() {
seq += &format!(":{}", event_type.unwrap_or(1));
}
if let Some(text) = text_codepoints {
let joined = text
.iter()
.map(|c| c.to_string())
.collect::<Vec<_>>()
.join(":");
seq += &format!(";{joined}");
}
seq += "u";
seq
}
#[test]
fn ctrl_f1_resolves_to_name_f1() {
let key = pk("\u{1b}[1;5P");
assert_eq!(key.name, "f1");
assert!(key.ctrl);
assert!(!key.shift);
assert!(!key.meta);
}
#[test]
fn ctrl_f2_resolves_to_name_f2() {
let key = pk("\u{1b}[1;5Q");
assert_eq!(key.name, "f2");
assert!(key.ctrl);
}
#[test]
fn ctrl_f3_resolves_to_name_f3() {
let key = pk("\u{1b}[1;5R");
assert_eq!(key.name, "f3");
assert!(key.ctrl);
}
#[test]
fn ctrl_f4_resolves_to_name_f4() {
let key = pk("\u{1b}[1;5S");
assert_eq!(key.name, "f4");
assert!(key.ctrl);
}
#[test]
fn unmapped_ctrl_sequence_returns_empty_name() {
let key = pk("\u{1b}[1;5I");
assert_eq!(key.name, "");
assert!(key.ctrl);
}
#[test]
fn another_unmapped_ctrl_sequence_returns_empty_name() {
let key = pk("\u{1b}[1;5X");
assert_eq!(key.name, "");
assert!(key.ctrl);
}
#[test]
fn shift_f1_resolves_to_name_f1_with_shift() {
let key = pk("\u{1b}[1;2P");
assert_eq!(key.name, "f1");
assert!(key.shift);
assert!(!key.ctrl);
}
#[test]
fn fn_branch_a_zero_modifier_sets_all_flags() {
let key = pk("\u{1b}[1;0~");
assert!(key.ctrl);
assert!(key.meta);
assert!(key.shift);
}
#[test]
fn fn_branch_b_zero_modifier_sets_all_flags() {
let key = pk("\u{1b}[1;0A");
assert!(key.ctrl);
assert!(key.meta);
assert!(key.shift);
}
#[test]
fn parser_feed_zero_modifier_fn_key_does_not_panic() {
let mut parser = crate::input::Parser::new();
let events = parser.feed(b"\x1b[1;0~");
let key = events
.into_iter()
.find_map(|e| match e {
crate::input::InputEvent::Key(k) => Some(k),
crate::input::InputEvent::Paste(_) => None,
})
.expect("a Key event for the complete CSI fn-key segment");
assert!(key.ctrl);
assert!(key.meta);
assert!(key.shift);
}
#[test]
fn kitty_simple_character() {
let result = pk(&kitty_key(97, None, None, None));
assert_eq!(result.name, "a");
assert!(!result.ctrl);
assert!(!result.shift);
assert!(!result.meta);
assert_eq!(result.event_type, Some(EventType::Press));
assert!(result.is_kitty_protocol);
}
#[test]
fn kitty_uppercase_character_shift() {
let result = pk(&kitty_key(65, Some(2), None, None));
assert_eq!(result.name, "a");
assert!(result.shift);
assert!(!result.ctrl);
assert_eq!(result.event_type, Some(EventType::Press));
}
#[test]
fn kitty_ctrl_modifier() {
let result = pk(&kitty_key(97, Some(5), None, None));
assert_eq!(result.name, "a");
assert!(result.ctrl);
assert!(!result.shift);
assert_eq!(result.event_type, Some(EventType::Press));
}
#[test]
fn kitty_alt_option_modifier() {
let result = pk(&kitty_key(97, Some(3), None, None));
assert_eq!(result.name, "a");
assert!(result.meta);
assert!(!result.ctrl);
assert_eq!(result.event_type, Some(EventType::Press));
}
#[test]
fn kitty_super_modifier() {
let result = pk(&kitty_key(97, Some(9), None, None));
assert_eq!(result.name, "a");
assert!(result.super_key);
assert!(!result.ctrl);
assert_eq!(result.event_type, Some(EventType::Press));
}
#[test]
fn kitty_hyper_modifier() {
let result = pk(&kitty_key(97, Some(17), None, None));
assert_eq!(result.name, "a");
assert!(result.hyper);
assert!(!result.super_key);
assert_eq!(result.event_type, Some(EventType::Press));
}
#[test]
fn kitty_meta_modifier() {
let result = pk(&kitty_key(97, Some(33), None, None));
assert_eq!(result.name, "a");
assert!(result.meta);
assert_eq!(result.event_type, Some(EventType::Press));
}
#[test]
fn kitty_caps_lock() {
let result = pk(&kitty_key(97, Some(65), None, None));
assert_eq!(result.name, "a");
assert!(result.caps_lock);
assert_eq!(result.event_type, Some(EventType::Press));
}
#[test]
fn kitty_num_lock() {
let result = pk(&kitty_key(97, Some(129), None, None));
assert_eq!(result.name, "a");
assert!(result.num_lock);
assert_eq!(result.event_type, Some(EventType::Press));
}
#[test]
fn kitty_combined_modifiers_ctrl_shift() {
let result = pk(&kitty_key(97, Some(6), None, None));
assert_eq!(result.name, "a");
assert!(result.ctrl);
assert!(result.shift);
assert!(!result.meta);
assert_eq!(result.event_type, Some(EventType::Press));
}
#[test]
fn kitty_combined_modifiers_super_ctrl() {
let result = pk(&kitty_key(115, Some(13), None, None));
assert_eq!(result.name, "s");
assert!(result.super_key);
assert!(result.ctrl);
assert!(!result.shift);
assert_eq!(result.event_type, Some(EventType::Press));
}
#[test]
fn kitty_escape_key() {
let result = pk(&kitty_key(27, None, None, None));
assert_eq!(result.name, "escape");
assert_eq!(result.event_type, Some(EventType::Press));
}
#[test]
fn kitty_return_enter_key() {
let result = pk(&kitty_key(13, None, None, None));
assert_eq!(result.name, "return");
assert_eq!(result.event_type, Some(EventType::Press));
}
#[test]
fn kitty_tab_key() {
let result = pk(&kitty_key(9, None, None, None));
assert_eq!(result.name, "tab");
assert_eq!(result.event_type, Some(EventType::Press));
}
#[test]
fn kitty_backspace_key() {
let result = pk(&kitty_key(8, None, None, None));
assert_eq!(result.name, "backspace");
assert_eq!(result.event_type, Some(EventType::Press));
}
#[test]
fn kitty_backspace_key_codepoint_127() {
let result = pk(&kitty_key(127, None, None, None));
assert_eq!(result.name, "backspace");
assert_eq!(result.event_type, Some(EventType::Press));
}
#[test]
fn legacy_meta_plus_backspace_0x7f() {
let result = pk("\u{1b}\u{7f}");
assert_eq!(result.name, "backspace");
assert!(result.meta);
}
#[test]
fn kitty_space_key() {
let result = pk(&kitty_key(32, None, None, None));
assert_eq!(result.name, "space");
assert_eq!(result.event_type, Some(EventType::Press));
}
#[test]
fn kitty_event_type_press() {
let result = pk(&kitty_key(97, Some(1), Some(1), None));
assert_eq!(result.name, "a");
assert_eq!(result.event_type, Some(EventType::Press));
}
#[test]
fn kitty_event_type_repeat() {
let result = pk(&kitty_key(97, Some(1), Some(2), None));
assert_eq!(result.name, "a");
assert_eq!(result.event_type, Some(EventType::Repeat));
}
#[test]
fn kitty_event_type_release() {
let result = pk(&kitty_key(97, Some(1), Some(3), None));
assert_eq!(result.name, "a");
assert_eq!(result.event_type, Some(EventType::Release));
}
#[test]
fn kitty_number_keys() {
let result = pk(&kitty_key(49, None, None, None));
assert_eq!(result.name, "1");
assert_eq!(result.event_type, Some(EventType::Press));
}
#[test]
fn kitty_special_character() {
let result = pk(&kitty_key(64, None, None, None));
assert_eq!(result.name, "@");
assert_eq!(result.event_type, Some(EventType::Press));
}
#[test]
fn kitty_ctrl_plus_letter_produces_codepoint_1_to_26() {
let result = pk(&kitty_key(1, Some(5), None, None));
assert_eq!(result.name, "a");
assert!(result.ctrl);
}
#[test]
fn kitty_preserves_sequence_and_raw() {
let seq = kitty_key(97, Some(5), None, None);
let result = pk(&seq);
assert_eq!(result.sequence, seq);
assert_eq!(result.raw, Some(seq));
}
#[test]
fn kitty_text_as_codepoints_field() {
let result = pk(&kitty_key(97, Some(2), Some(1), Some(&[65])));
assert_eq!(result.name, "a");
assert_eq!(result.text.as_deref(), Some("A"));
assert!(result.shift);
assert!(result.is_kitty_protocol);
}
#[test]
fn kitty_text_as_codepoints_with_multiple_codepoints() {
let result = pk(&kitty_key(97, Some(1), Some(1), Some(&[72, 101])));
assert_eq!(result.text.as_deref(), Some("He"));
assert!(result.is_kitty_protocol);
}
#[test]
fn kitty_supplementary_unicode_codepoint() {
let result = pk(&kitty_key(128_512, None, None, None));
assert_eq!(result.name, "😀");
assert!(result.is_kitty_protocol);
}
#[test]
fn kitty_text_as_codepoints_with_supplementary_unicode() {
let result = pk(&kitty_key(97, Some(1), Some(1), Some(&[128_512])));
assert_eq!(result.text.as_deref(), Some("😀"));
assert!(result.is_kitty_protocol);
}
#[test]
fn kitty_text_defaults_to_character_from_codepoint() {
let result = pk(&kitty_key(97, None, None, None));
assert_eq!(result.text.as_deref(), Some("a"));
assert!(result.is_kitty_protocol);
}
#[test]
fn kitty_arrow_keys_with_event_type() {
let up = pk("\u{1b}[1;1:1A");
assert_eq!(up.name, "up");
assert_eq!(up.event_type, Some(EventType::Press));
assert!(up.is_kitty_protocol);
let down = pk("\u{1b}[1;1:3B");
assert_eq!(down.name, "down");
assert_eq!(down.event_type, Some(EventType::Release));
assert!(down.is_kitty_protocol);
let right = pk("\u{1b}[1;1:2C");
assert_eq!(right.name, "right");
assert_eq!(right.event_type, Some(EventType::Repeat));
assert!(right.is_kitty_protocol);
let left = pk("\u{1b}[1;1:1D");
assert_eq!(left.name, "left");
assert_eq!(left.event_type, Some(EventType::Press));
assert!(left.is_kitty_protocol);
}
#[test]
fn kitty_arrow_keys_with_modifiers() {
let result = pk("\u{1b}[1;5:1A");
assert_eq!(result.name, "up");
assert!(result.ctrl);
assert_eq!(result.event_type, Some(EventType::Press));
assert!(result.is_kitty_protocol);
}
#[test]
fn kitty_home_and_end_keys() {
let home = pk("\u{1b}[1;1:1H");
assert_eq!(home.name, "home");
assert_eq!(home.event_type, Some(EventType::Press));
assert!(home.is_kitty_protocol);
let end = pk("\u{1b}[1;1:1F");
assert_eq!(end.name, "end");
assert_eq!(end.event_type, Some(EventType::Press));
assert!(end.is_kitty_protocol);
}
#[test]
fn kitty_tilde_terminated_special_keys() {
let del = pk("\u{1b}[3;1:1~");
assert_eq!(del.name, "delete");
assert_eq!(del.event_type, Some(EventType::Press));
assert!(del.is_kitty_protocol);
let ins = pk("\u{1b}[2;1:1~");
assert_eq!(ins.name, "insert");
assert!(ins.is_kitty_protocol);
let pgup = pk("\u{1b}[5;1:1~");
assert_eq!(pgup.name, "pageup");
assert!(pgup.is_kitty_protocol);
let f5 = pk("\u{1b}[15;1:1~");
assert_eq!(f5.name, "f5");
assert!(f5.is_kitty_protocol);
}
#[test]
fn kitty_tilde_keys_with_modifiers() {
let result = pk("\u{1b}[3;2:1~");
assert_eq!(result.name, "delete");
assert!(result.shift);
assert_eq!(result.event_type, Some(EventType::Press));
assert!(result.is_kitty_protocol);
}
#[test]
fn kitty_invalid_codepoint_above_max_returns_safe_empty_keypress() {
let result = pk("\u{1b}[1114112u");
assert_eq!(result.name, "");
assert!(!result.ctrl);
assert!(result.is_kitty_protocol);
assert_eq!(result.is_printable, Some(false));
}
#[test]
fn kitty_surrogate_codepoint_returns_safe_empty_keypress() {
let result = pk("\u{1b}[55296u");
assert_eq!(result.name, "");
assert!(!result.ctrl);
assert!(result.is_kitty_protocol);
assert_eq!(result.is_printable, Some(false));
}
#[test]
fn kitty_invalid_text_codepoint_replaced_with_fallback() {
let result = pk(&kitty_key(97, Some(1), Some(1), Some(&[1_114_112])));
assert_eq!(result.name, "a");
assert_eq!(result.text.as_deref(), Some("?"));
assert!(result.is_kitty_protocol);
}
#[test]
fn kitty_malformed_modifier_0_does_not_set_all_flags() {
let result = pk("\u{1b}[97;0u");
assert_eq!(result.name, "a");
assert!(!result.ctrl);
assert!(!result.shift);
assert!(!result.meta);
assert!(!result.super_key);
assert!(result.is_kitty_protocol);
}
#[test]
fn non_kitty_sequences_fall_back_to_legacy_parsing() {
let result = pk("\u{1b}[A");
assert_eq!(result.name, "up");
assert!(!result.is_kitty_protocol);
}
#[test]
fn non_kitty_sequences_ctrl_c() {
let result = pk("\u{3}");
assert_eq!(result.name, "c");
assert!(result.ctrl);
assert!(!result.is_kitty_protocol);
}
#[test]
fn kitty_is_printable_is_true_for_regular_characters() {
assert_eq!(
pk(&kitty_key(97, None, None, None)).is_printable,
Some(true)
);
}
#[test]
fn kitty_is_printable_is_true_for_digits() {
assert_eq!(
pk(&kitty_key(49, None, None, None)).is_printable,
Some(true)
);
}
#[test]
fn kitty_is_printable_is_true_for_symbols() {
assert_eq!(
pk(&kitty_key(64, None, None, None)).is_printable,
Some(true)
);
}
#[test]
fn kitty_is_printable_is_true_for_emoji() {
assert_eq!(
pk(&kitty_key(128_512, None, None, None)).is_printable,
Some(true)
);
}
#[test]
fn kitty_is_printable_is_false_for_escape() {
assert_eq!(
pk(&kitty_key(27, None, None, None)).is_printable,
Some(false)
);
}
#[test]
fn kitty_is_printable_is_true_for_return() {
assert_eq!(
pk(&kitty_key(13, None, None, None)).is_printable,
Some(true)
);
}
#[test]
fn kitty_is_printable_is_false_for_tab() {
assert_eq!(
pk(&kitty_key(9, None, None, None)).is_printable,
Some(false)
);
}
#[test]
fn kitty_is_printable_is_true_for_space() {
assert_eq!(
pk(&kitty_key(32, None, None, None)).is_printable,
Some(true)
);
}
#[test]
fn kitty_is_printable_is_false_for_backspace() {
assert_eq!(
pk(&kitty_key(8, None, None, None)).is_printable,
Some(false)
);
}
#[test]
fn kitty_is_printable_is_false_for_ctrl_letter() {
assert_eq!(
pk(&kitty_key(1, Some(5), None, None)).is_printable,
Some(false)
);
}
#[test]
fn kitty_is_printable_is_false_for_special_keys_arrows() {
assert_eq!(pk("\u{1b}[1;1:1A").is_printable, Some(false));
}
#[test]
fn kitty_capslock_is_non_printable() {
let result = pk("\u{1b}[57358u");
assert_eq!(result.name, "capslock");
assert_eq!(result.is_printable, Some(false));
assert!(result.is_kitty_protocol);
}
#[test]
fn kitty_printscreen_is_non_printable() {
let result = pk("\u{1b}[57361u");
assert_eq!(result.name, "printscreen");
assert_eq!(result.is_printable, Some(false));
assert!(result.is_kitty_protocol);
}
#[test]
fn kitty_f13_is_non_printable() {
let result = pk("\u{1b}[57376u");
assert_eq!(result.name, "f13");
assert_eq!(result.is_printable, Some(false));
assert!(result.is_kitty_protocol);
}
#[test]
fn kitty_media_key_is_non_printable() {
let result = pk("\u{1b}[57428u");
assert_eq!(result.name, "mediaplay");
assert_eq!(result.is_printable, Some(false));
assert!(result.is_kitty_protocol);
}
#[test]
fn kitty_modifier_only_leftshift_is_non_printable() {
let result = pk("\u{1b}[57441u");
assert_eq!(result.name, "leftshift");
assert_eq!(result.is_printable, Some(false));
assert!(result.is_kitty_protocol);
}
#[test]
fn kitty_modifier_only_leftcontrol_is_non_printable() {
let result = pk("\u{1b}[57442u");
assert_eq!(result.name, "leftcontrol");
assert_eq!(result.is_printable, Some(false));
assert!(result.is_kitty_protocol);
}
#[test]
fn kitty_kp_keys_are_non_printable() {
let result = pk("\u{1b}[57399u");
assert_eq!(result.name, "kp0");
assert_eq!(result.is_printable, Some(false));
assert!(result.is_kitty_protocol);
}
#[test]
fn kitty_scrolllock_is_non_printable() {
let result = pk("\u{1b}[57359u");
assert_eq!(result.name, "scrolllock");
assert_eq!(result.is_printable, Some(false));
assert!(result.is_kitty_protocol);
}
#[test]
fn kitty_numlock_is_non_printable() {
let result = pk("\u{1b}[57360u");
assert_eq!(result.name, "numlock");
assert_eq!(result.is_printable, Some(false));
assert!(result.is_kitty_protocol);
}
#[test]
fn kitty_pause_is_non_printable() {
let result = pk("\u{1b}[57362u");
assert_eq!(result.name, "pause");
assert_eq!(result.is_printable, Some(false));
assert!(result.is_kitty_protocol);
}
#[test]
fn kitty_volume_keys_are_non_printable() {
let lower = pk("\u{1b}[57438u");
assert_eq!(lower.name, "lowervolume");
assert_eq!(lower.is_printable, Some(false));
let raise = pk("\u{1b}[57439u");
assert_eq!(raise.name, "raisevolume");
assert_eq!(raise.is_printable, Some(false));
let mute = pk("\u{1b}[57440u");
assert_eq!(mute.name, "mutevolume");
assert_eq!(mute.is_printable, Some(false));
}
#[test]
fn kitty_space_key_has_text_field_set_to_space_character() {
assert_eq!(
pk(&kitty_key(32, None, None, None)).text.as_deref(),
Some(" ")
);
}
#[test]
fn kitty_return_key_has_text_field_set_to_carriage_return() {
assert_eq!(
pk(&kitty_key(13, None, None, None)).text.as_deref(),
Some("\r")
);
}