403 lines
12 KiB
Rust
403 lines
12 KiB
Rust
use super::*;
|
|
|
|
/// A key pressed with modifiers written as `ctrl+alt+shift+name`.
|
|
fn key(spec: &'static str, text: &'static str) -> Key<'static> {
|
|
let mut parts: Vec<&str> = spec.split('+').collect();
|
|
let name = parts.pop().expect("a name");
|
|
Key {
|
|
name,
|
|
text,
|
|
shift: parts.contains(&"shift"),
|
|
ctrl: parts.contains(&"ctrl"),
|
|
alt: parts.contains(&"alt"),
|
|
platform: parts.contains(&"cmd"),
|
|
action: Action::Press,
|
|
unshifted: None,
|
|
}
|
|
}
|
|
|
|
fn legacy(key: Key, app_cursor: bool) -> Option<String> {
|
|
encode(&key, app_cursor, Kitty::default()).map(|bytes| String::from_utf8(bytes).expect("utf-8"))
|
|
}
|
|
|
|
fn kitty(key: Key, flags: Kitty) -> Option<String> {
|
|
encode(&key, false, flags).map(|bytes| String::from_utf8(bytes).expect("utf-8"))
|
|
}
|
|
|
|
const DISAMBIGUATE: Kitty = Kitty {
|
|
disambiguate: true,
|
|
events: false,
|
|
alternates: false,
|
|
all: false,
|
|
text: false,
|
|
};
|
|
|
|
#[test]
|
|
fn plain_text_is_left_to_the_input_method() {
|
|
assert_eq!(legacy(key("a", "a"), false), None);
|
|
assert_eq!(legacy(key("shift+a", "A"), false), None);
|
|
assert_eq!(legacy(key("space", " "), false), None);
|
|
assert_eq!(legacy(key("ctrl+a", ""), false).as_deref(), Some("\x01"));
|
|
let released = Key {
|
|
action: Action::Release,
|
|
..key("enter", "\n")
|
|
};
|
|
assert_eq!(legacy(released, false), None, "legacy sends no releases");
|
|
}
|
|
|
|
#[test]
|
|
fn cursor_keys_follow_decckm_and_modifiers() {
|
|
assert_eq!(legacy(key("up", ""), false).as_deref(), Some("\x1b[A"));
|
|
assert_eq!(legacy(key("up", ""), true).as_deref(), Some("\x1bOA"));
|
|
assert_eq!(
|
|
legacy(key("ctrl+up", ""), true).as_deref(),
|
|
Some("\x1b[1;5A")
|
|
);
|
|
assert_eq!(
|
|
legacy(key("alt+shift+left", ""), false).as_deref(),
|
|
Some("\x1b[1;4D")
|
|
);
|
|
assert_eq!(legacy(key("home", ""), true).as_deref(), Some("\x1bOH"));
|
|
assert_eq!(legacy(key("end", ""), false).as_deref(), Some("\x1b[F"));
|
|
}
|
|
|
|
#[test]
|
|
fn function_and_editing_keys_send_xterm_sequences() {
|
|
let cases = [
|
|
("f1", "\x1bOP"),
|
|
("ctrl+f1", "\x1b[1;5P"),
|
|
("f3", "\x1bOR"),
|
|
("shift+f3", "\x1b[13;2~"),
|
|
("f4", "\x1bOS"),
|
|
("f5", "\x1b[15~"),
|
|
("shift+f5", "\x1b[15;2~"),
|
|
("f12", "\x1b[24~"),
|
|
("f25", "\x1b[46~"),
|
|
("insert", "\x1b[2~"),
|
|
("delete", "\x1b[3~"),
|
|
("ctrl+pageup", "\x1b[5;5~"),
|
|
("pagedown", "\x1b[6~"),
|
|
];
|
|
for (spec, want) in cases {
|
|
assert_eq!(
|
|
legacy(key(spec, ""), false).as_deref(),
|
|
Some(want),
|
|
"{spec}"
|
|
);
|
|
}
|
|
assert_eq!(legacy(key("f26", ""), false), None);
|
|
}
|
|
|
|
#[test]
|
|
fn enter_tab_escape_and_backspace_carry_their_modifiers() {
|
|
let cases: [(&str, &str, &[u8]); 15] = [
|
|
("enter", "\n", b"\r"),
|
|
("shift+enter", "\n", b"\x1b[27;2;13~"),
|
|
("alt+enter", "\n", b"\x1b\r"),
|
|
("ctrl+enter", "", b"\x1b[27;5;13~"),
|
|
("tab", "\t", b"\t"),
|
|
("shift+tab", "", b"\x1b[Z"),
|
|
("alt+tab", "\t", b"\x1b\t"),
|
|
("ctrl+tab", "", b"\x1b[27;5;9~"),
|
|
("escape", "", b"\x1b"),
|
|
("alt+escape", "", b"\x1b\x1b"),
|
|
("shift+escape", "", b"\x1b[27;2;27~"),
|
|
("backspace", "", b"\x7f"),
|
|
("ctrl+backspace", "", b"\x08"),
|
|
("alt+backspace", "", b"\x1b\x7f"),
|
|
("ctrl+alt+backspace", "", b"\x1b\x08"),
|
|
];
|
|
for (spec, text, want) in cases {
|
|
assert_eq!(
|
|
encode(&key(spec, text), false, Kitty::default()).as_deref(),
|
|
Some(want),
|
|
"{spec}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn ctrl_makes_c0_bytes_or_fixterms_csi_u() {
|
|
let bytes = |spec| encode(&key(spec, ""), false, Kitty::default());
|
|
assert_eq!(bytes("ctrl+c"), Some(vec![0x03]));
|
|
assert_eq!(bytes("ctrl+alt+c"), Some(vec![0x1B, 0x03]));
|
|
assert_eq!(bytes("ctrl+space"), Some(vec![0x00]));
|
|
assert_eq!(bytes("ctrl+_"), Some(vec![31]));
|
|
assert_eq!(bytes("ctrl+?"), Some(vec![127]));
|
|
assert_eq!(bytes("ctrl+2"), Some(vec![0x00]));
|
|
assert_eq!(bytes("ctrl+["), Some(b"\x1b[91;5u".to_vec()));
|
|
assert_eq!(bytes("ctrl+i"), Some(b"\x1b[105;5u".to_vec()));
|
|
assert_eq!(bytes("ctrl+m"), Some(b"\x1b[109;5u".to_vec()));
|
|
assert_eq!(bytes("ctrl+shift+a"), Some(b"\x1b[97;6u".to_vec()));
|
|
}
|
|
|
|
#[test]
|
|
fn alt_prefixes_escape_to_what_the_key_types() {
|
|
assert_eq!(legacy(key("alt+a", "a"), false).as_deref(), Some("\x1ba"));
|
|
assert_eq!(
|
|
legacy(key("alt+shift+a", ""), false).as_deref(),
|
|
Some("\x1bA")
|
|
);
|
|
assert_eq!(legacy(key("alt+e", "é"), false).as_deref(), Some("\x1bé"));
|
|
assert_eq!(
|
|
legacy(key("alt+space", " "), false).as_deref(),
|
|
Some("\x1b ")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn kitty_disambiguate_keeps_text_and_the_legacy_three() {
|
|
assert_eq!(kitty(key("a", "a"), DISAMBIGUATE), None);
|
|
assert_eq!(kitty(key("shift+a", "A"), DISAMBIGUATE), None);
|
|
assert_eq!(
|
|
kitty(key("enter", "\n"), DISAMBIGUATE).as_deref(),
|
|
Some("\r")
|
|
);
|
|
assert_eq!(kitty(key("tab", "\t"), DISAMBIGUATE).as_deref(), Some("\t"));
|
|
assert_eq!(
|
|
kitty(key("backspace", ""), DISAMBIGUATE).as_deref(),
|
|
Some("\x7f")
|
|
);
|
|
assert_eq!(
|
|
kitty(key("escape", ""), DISAMBIGUATE).as_deref(),
|
|
Some("\x1b[27u")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn kitty_reports_modified_keys_as_csi_u() {
|
|
let cases = [
|
|
("ctrl+a", "", "\x1b[97;5u"),
|
|
("alt+a", "a", "\x1b[97;3u"),
|
|
("ctrl+shift+a", "", "\x1b[97;6u"),
|
|
("shift+enter", "\n", "\x1b[13;2u"),
|
|
("shift+tab", "", "\x1b[9;2u"),
|
|
("up", "", "\x1b[A"),
|
|
("ctrl+up", "", "\x1b[1;5A"),
|
|
("f1", "", "\x1b[P"),
|
|
("f5", "", "\x1b[15~"),
|
|
("ctrl+f5", "", "\x1b[15;5~"),
|
|
("f13", "", "\x1b[57376u"),
|
|
("f25", "", "\x1b[57388u"),
|
|
];
|
|
for (spec, text, want) in cases {
|
|
assert_eq!(
|
|
kitty(key(spec, text), DISAMBIGUATE).as_deref(),
|
|
Some(want),
|
|
"{spec}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn kitty_events_report_repeats_and_releases() {
|
|
let flags = Kitty {
|
|
events: true,
|
|
..DISAMBIGUATE
|
|
};
|
|
let with = |spec, text, action| Key {
|
|
action,
|
|
..key(spec, text)
|
|
};
|
|
assert_eq!(
|
|
kitty(with("a", "a", Action::Repeat), flags),
|
|
None,
|
|
"a repeat types again"
|
|
);
|
|
assert_eq!(
|
|
kitty(with("a", "a", Action::Release), flags).as_deref(),
|
|
Some("\x1b[97;1:3u")
|
|
);
|
|
assert_eq!(
|
|
kitty(with("ctrl+a", "", Action::Repeat), flags).as_deref(),
|
|
Some("\x1b[97;5:2u")
|
|
);
|
|
assert_eq!(kitty(with("enter", "\n", Action::Release), flags), None);
|
|
assert_eq!(
|
|
kitty(with("up", "", Action::Release), flags).as_deref(),
|
|
Some("\x1b[1;1:3A")
|
|
);
|
|
assert_eq!(kitty(with("a", "a", Action::Release), DISAMBIGUATE), None);
|
|
}
|
|
|
|
#[test]
|
|
fn kitty_report_all_alternates_and_text() {
|
|
let all = Kitty {
|
|
all: true,
|
|
..DISAMBIGUATE
|
|
};
|
|
assert_eq!(kitty(key("a", "a"), all).as_deref(), Some("\x1b[97u"));
|
|
assert_eq!(
|
|
kitty(key("shift+a", "A"), all).as_deref(),
|
|
Some("\x1b[97;2u")
|
|
);
|
|
assert_eq!(kitty(key("enter", "\n"), all).as_deref(), Some("\x1b[13u"));
|
|
let alternates = Kitty {
|
|
alternates: true,
|
|
..all
|
|
};
|
|
assert_eq!(
|
|
kitty(key("shift+a", "A"), alternates).as_deref(),
|
|
Some("\x1b[97:65;2u")
|
|
);
|
|
let text = Kitty { text: true, ..all };
|
|
assert_eq!(kitty(key("a", "a"), text).as_deref(), Some("\x1b[97;;97u"));
|
|
assert_eq!(
|
|
kitty(key("shift+a", "A"), text).as_deref(),
|
|
Some("\x1b[97;2;65u")
|
|
);
|
|
assert_eq!(
|
|
kitty(key("ctrl+a", ""), text).as_deref(),
|
|
Some("\x1b[97;5u")
|
|
);
|
|
let released = Key {
|
|
action: Action::Release,
|
|
..key("enter", "\n")
|
|
};
|
|
let everything = Kitty {
|
|
events: true,
|
|
..all
|
|
};
|
|
assert_eq!(kitty(released, everything).as_deref(), Some("\x1b[13;1:3u"));
|
|
}
|
|
|
|
#[test]
|
|
fn flags_that_only_add_fields_keep_legacy_keys() {
|
|
let alternates = Kitty {
|
|
alternates: true,
|
|
..Kitty::default()
|
|
};
|
|
let bytes = |spec, flags, app_cursor| encode(&key(spec, ""), app_cursor, flags);
|
|
assert_eq!(bytes("ctrl+c", alternates, false), Some(vec![0x03]));
|
|
assert_eq!(bytes("escape", alternates, false), Some(b"\x1b".to_vec()));
|
|
assert_eq!(bytes("up", alternates, true), Some(b"\x1bOA".to_vec()));
|
|
assert_eq!(bytes("f1", alternates, false), Some(b"\x1bOP".to_vec()));
|
|
assert_eq!(
|
|
bytes("ctrl+shift+a", alternates, false),
|
|
Some(b"\x1b[97:65;6u".to_vec())
|
|
);
|
|
let events = Kitty {
|
|
events: true,
|
|
..Kitty::default()
|
|
};
|
|
let released = |spec| Key {
|
|
action: Action::Release,
|
|
..key(spec, "")
|
|
};
|
|
assert_eq!(bytes("ctrl+c", events, false), Some(vec![0x03]));
|
|
assert_eq!(
|
|
encode(&released("ctrl+c"), false, events),
|
|
Some(b"\x1b[99;5:3u".to_vec())
|
|
);
|
|
assert_eq!(bytes("f1", events, false), Some(b"\x1b[P".to_vec()));
|
|
assert_eq!(bytes("escape", events, false), Some(b"\x1b".to_vec()));
|
|
assert_eq!(
|
|
encode(&released("escape"), false, events),
|
|
Some(b"\x1b[27;1:3u".to_vec())
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn kitty_reports_the_key_shift_turned_into_a_symbol() {
|
|
// gpui names Shift-[ by its `{` and drops Shift; the platform gives back `[`.
|
|
let made = |spec, text, action| Key {
|
|
unshifted: Some('['),
|
|
action,
|
|
..key(spec, text)
|
|
};
|
|
let herdr = Kitty {
|
|
events: true,
|
|
alternates: true,
|
|
..DISAMBIGUATE
|
|
};
|
|
assert_eq!(
|
|
kitty(made("cmd+ctrl+{", "", Action::Press), herdr).as_deref(),
|
|
Some("\x1b[91:123;14u")
|
|
);
|
|
assert_eq!(
|
|
kitty(made("cmd+ctrl+{", "", Action::Release), herdr).as_deref(),
|
|
Some("\x1b[91:123;14:3u")
|
|
);
|
|
assert_eq!(
|
|
kitty(made("ctrl+{", "", Action::Press), DISAMBIGUATE).as_deref(),
|
|
Some("\x1b[91;6u")
|
|
);
|
|
assert_eq!(
|
|
kitty(made("{", "{", Action::Press), herdr),
|
|
None,
|
|
"it types"
|
|
);
|
|
let all = Kitty {
|
|
all: true,
|
|
alternates: true,
|
|
..DISAMBIGUATE
|
|
};
|
|
assert_eq!(
|
|
kitty(made("{", "{", Action::Press), all).as_deref(),
|
|
Some("\x1b[91:123;2u")
|
|
);
|
|
assert_eq!(
|
|
legacy(made("ctrl+{", "", Action::Press), false).as_deref(),
|
|
Some("\x1b[123;5u"),
|
|
"legacy keeps fixterms' shifted character"
|
|
);
|
|
let events = Kitty {
|
|
events: true,
|
|
..Kitty::default()
|
|
};
|
|
assert_eq!(
|
|
kitty(made("alt+{", "", Action::Press), events).as_deref(),
|
|
Some("\x1b{"),
|
|
"a flag that adds no field keeps the legacy bytes gpui's name gives"
|
|
);
|
|
let bang = Key {
|
|
unshifted: Some('1'),
|
|
..key("alt+!", "")
|
|
};
|
|
assert_eq!(kitty(bang, events).as_deref(), Some("\x1b!"));
|
|
let same = Key {
|
|
unshifted: Some('7'),
|
|
..key("ctrl+7", "")
|
|
};
|
|
assert_eq!(
|
|
kitty(same, herdr).as_deref(),
|
|
Some("\x1b[55;6u"),
|
|
"no alternate where Shift made the same character"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn super_is_kittys_eighth_bit() {
|
|
let events = Kitty {
|
|
events: true,
|
|
..DISAMBIGUATE
|
|
};
|
|
assert_eq!(
|
|
kitty(key("cmd+ctrl+c", ""), DISAMBIGUATE).as_deref(),
|
|
Some("\x1b[99;13u")
|
|
);
|
|
let released = Key {
|
|
action: Action::Release,
|
|
..key("cmd+ctrl+c", "")
|
|
};
|
|
assert_eq!(kitty(released, events).as_deref(), Some("\x1b[99;13:3u"));
|
|
assert_eq!(legacy(key("cmd+c", ""), false), None, "Cmd types nothing");
|
|
assert_eq!(
|
|
legacy(key("cmd+up", ""), false).as_deref(),
|
|
Some("\x1b[1;9A")
|
|
);
|
|
assert_eq!(
|
|
legacy(key("cmd+enter", ""), false).as_deref(),
|
|
Some("\x1b[27;9;13~")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn pastes_end_lines_with_cr_or_go_bracketed_without_escapes() {
|
|
assert_eq!(paste("a\r\nb\nc", false), b"a\rb\rc");
|
|
assert_eq!(
|
|
paste("x\n\x1b[201~rm\x03", true),
|
|
b"\x1b[200~x\n[201~rm\x1b[201~"
|
|
);
|
|
}
|