1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-23 21:32:13 +03:00

Adopt CSI u modifier encoding for keypresses

See http://www.leonerd.org.uk/hacks/fixterms/ for the specification.

Refs: https://github.com/wez/wezterm/issues/63
This commit is contained in:
Wez Furlong 2019-11-16 11:47:45 -08:00
parent 9e3c6a4ef9
commit 6289c08a4e
4 changed files with 219 additions and 78 deletions

View File

@ -436,6 +436,13 @@ impl TermWindow {
WK::Char(c) => KC::Char(*c), WK::Char(c) => KC::Char(*c),
WK::Composed(ref s) => { WK::Composed(ref s) => {
let mut chars = s.chars();
if let Some(first_char) = chars.next() {
if chars.next().is_none() {
// Was just a single char after all
return self.win_key_code_to_termwiz_key_code(&WK::Char(first_char));
}
}
return Key::Composed(s.to_owned()); return Key::Composed(s.to_owned());
} }
WK::Function(f) => KC::Function(*f), WK::Function(f) => KC::Function(*f),

View File

@ -834,17 +834,70 @@ impl TerminalState {
// Normalize the modifier state for Char's that are uppercase; remove // Normalize the modifier state for Char's that are uppercase; remove
// the SHIFT modifier so that reduce ambiguity below // the SHIFT modifier so that reduce ambiguity below
let mods = match key { let mods = match key {
Char(c) if c.is_ascii_uppercase() && mods.contains(KeyModifiers::SHIFT) => { Char(c)
if (c.is_ascii_punctuation() || c.is_ascii_uppercase())
&& mods.contains(KeyModifiers::SHIFT) =>
{
mods & !KeyModifiers::SHIFT mods & !KeyModifiers::SHIFT
} }
_ => mods, _ => mods,
}; };
fn encode_modifiers(mods: KeyModifiers) -> u8 {
let mut number = 0;
if mods.contains(KeyModifiers::SHIFT) {
number |= 1;
}
if mods.contains(KeyModifiers::ALT) {
number |= 2;
}
if mods.contains(KeyModifiers::CTRL) {
number |= 4;
}
number
}
let mut buf = String::new(); let mut buf = String::new();
// TODO: also respect self.application_keypad // TODO: also respect self.application_keypad
fn csi_u_encode(buf: &mut String, c: char, mods: KeyModifiers) -> Result<(), Error> {
write!(buf, "\x1b[{};{}u", c as u32, 1 + encode_modifiers(mods))?;
Ok(())
}
/// characters that when masked for CTRL could be an ascii control character
/// or could be a key that a user legitimately wants to process in their
/// terminal application
fn is_ambiguous_ascii_ctrl(c: char) -> bool {
match c {
'i' | 'I' | 'm' | 'M' | '[' | '{' | '@' => true,
_ => false,
}
}
let to_send = match key { let to_send = match key {
Char(c) if is_ambiguous_ascii_ctrl(c) && mods.contains(KeyModifiers::CTRL) => {
csi_u_encode(&mut buf, c, mods)?;
buf.as_str()
}
Char(c) if c.is_ascii_uppercase() && mods.contains(KeyModifiers::CTRL) => {
csi_u_encode(&mut buf, c, mods)?;
buf.as_str()
}
Char(c)
if (c.is_ascii_alphanumeric() || c.is_ascii_punctuation())
&& mods.contains(KeyModifiers::CTRL) =>
{
let c = ((c as u8) & 0x1f) as char;
if mods.contains(KeyModifiers::ALT) {
buf.push(0x1b as char);
}
buf.push(c);
buf.as_str()
}
// When alt is pressed, send escape first to indicate to the peer that // When alt is pressed, send escape first to indicate to the peer that
// ALT is pressed. We do this only for ascii alnum characters because // ALT is pressed. We do this only for ascii alnum characters because
// eg: on macOS generates altgr style glyphs and keeps the ALT key // eg: on macOS generates altgr style glyphs and keeps the ALT key
@ -858,76 +911,124 @@ impl TerminalState {
buf.push(c); buf.push(c);
buf.as_str() buf.as_str()
} }
Backspace if mods.contains(KeyModifiers::ALT) => "\x1b\x08",
Backspace => "\x08",
Tab => "\t", Enter | Escape | Backspace | Char('\x08') | Delete | Char('\x7f') => {
Enter => "\r", let c = match key {
Escape => "\x1b", Enter => '\r',
Escape => '\x1b',
// Delete Char('\x08') | Backspace => '\x08',
// TODO: application delete key? See https://github.com/wez/wezterm/issues/52 Char('\x7f') | Delete => '\x7f',
Char('\x7f') | Delete => "\x7f", // "\x1b[3~" _ => unreachable!(),
};
Char(c) if mods.contains(KeyModifiers::SHIFT) || mods.contains(KeyModifiers::CTRL) {
if c <= 0xff as char csi_u_encode(&mut buf, c, mods)?;
&& c > 0x40 as char } else {
&& mods == KeyModifiers::CTRL | KeyModifiers::SHIFT => if mods.contains(KeyModifiers::ALT) && key != Escape {
{ buf.push(0x1b as char);
// If shift is held we have C == 0x43 and want to translate }
// that into 0x03 buf.push(c);
buf.push((c as u8 - 0x40) as char); }
buf.as_str() buf.as_str()
} }
Char(c)
if c <= 0xff as char && c > 0x60 as char && mods.contains(KeyModifiers::CTRL) => Tab => {
{ if mods.contains(KeyModifiers::ALT) {
// If shift is not held we have C == 0x63 and want to translate buf.push(0x1b as char);
// that into 0x03 }
buf.push((c as u8 - 0x60) as char); let mods = mods & !KeyModifiers::ALT;
if mods == KeyModifiers::CTRL {
buf.push_str("\x1b[9;5u");
} else if mods == KeyModifiers::CTRL | KeyModifiers::SHIFT {
buf.push_str("\x1b[1;5Z");
} else if mods == KeyModifiers::SHIFT {
buf.push_str("\x1b[Z");
} else {
buf.push('\t');
}
buf.as_str() buf.as_str()
} }
Char(c) => { Char(c) => {
buf.push(c); if mods.is_empty() {
buf.push(c);
} else {
csi_u_encode(&mut buf, c, mods)?;
}
buf.as_str() buf.as_str()
} }
UpArrow if mods.contains(KeyModifiers::ALT) => "\x1b\x1b[A", Home
DownArrow if mods.contains(KeyModifiers::ALT) => "\x1b\x1b[B", | End
RightArrow if mods.contains(KeyModifiers::ALT) => "\x1b\x1b[C", | UpArrow
LeftArrow if mods.contains(KeyModifiers::ALT) => "\x1b\x1b[D", | DownArrow
UpArrow if self.application_cursor_keys => "\x1bOA", | RightArrow
DownArrow if self.application_cursor_keys => "\x1bOB", | LeftArrow
RightArrow if self.application_cursor_keys => "\x1bOC", | ApplicationUpArrow
LeftArrow if self.application_cursor_keys => "\x1bOD", | ApplicationDownArrow
UpArrow => "\x1b[A", | ApplicationRightArrow
DownArrow => "\x1b[B", | ApplicationLeftArrow => {
RightArrow => "\x1b[C", let (force_app, c) = match key {
LeftArrow => "\x1b[D", UpArrow => (false, 'A'),
ApplicationUpArrow => "\x1bOA", DownArrow => (false, 'B'),
ApplicationDownArrow => "\x1bOB", RightArrow => (false, 'C'),
ApplicationRightArrow => "\x1bOC", LeftArrow => (false, 'D'),
ApplicationLeftArrow => "\x1bOD", Home => (false, 'H'),
End => (false, 'F'),
ApplicationUpArrow => (true, 'A'),
ApplicationDownArrow => (true, 'B'),
ApplicationRightArrow => (true, 'C'),
ApplicationLeftArrow => (true, 'D'),
_ => unreachable!(),
};
PageUp if mods.contains(KeyModifiers::SHIFT) => { let csi_or_ss3 = if force_app || self.application_cursor_keys {
// Use SS3 in application mode
"\x1bO"
} else {
// otherwise use regular CSI
"\x1b["
};
if mods.contains(KeyModifiers::SHIFT) || mods.contains(KeyModifiers::CTRL) {
write!(buf, "{}1;{}{}", csi_or_ss3, 1 + encode_modifiers(mods), c)?;
} else {
if mods.contains(KeyModifiers::ALT) {
buf.push(0x1b as char);
}
write!(buf, "{}{}", csi_or_ss3, c)?;
}
buf.as_str()
}
PageUp if mods == KeyModifiers::SHIFT => {
let rows = self.screen().physical_rows as i64; let rows = self.screen().physical_rows as i64;
self.scroll_viewport(-rows); self.scroll_viewport(-rows);
"" ""
} }
PageDown if mods.contains(KeyModifiers::SHIFT) => { PageDown if mods == KeyModifiers::SHIFT => {
let rows = self.screen().physical_rows as i64; let rows = self.screen().physical_rows as i64;
self.scroll_viewport(rows); self.scroll_viewport(rows);
"" ""
} }
PageUp => "\x1b[5~",
PageDown => "\x1b[6~",
Home if self.application_cursor_keys => "\x1bOH", PageUp | PageDown | Insert => {
End if self.application_cursor_keys => "\x1bOF", let c = match key {
Home => "\x1b[H", Insert => 2,
End => "\x1b[F", PageUp => 5,
PageDown => 6,
_ => unreachable!(),
};
Insert => "\x1b[2~", if mods.contains(KeyModifiers::SHIFT) || mods.contains(KeyModifiers::CTRL) {
write!(buf, "\x1b[{};{}~", c, 1 + encode_modifiers(mods))?;
} else {
if mods.contains(KeyModifiers::ALT) {
buf.push(0x1b as char);
}
write!(buf, "\x1b[{}~", c)?;
}
buf.as_str()
}
Function(n) => { Function(n) => {
if mods.is_empty() && n < 5 { if mods.is_empty() && n < 5 {
@ -957,7 +1058,7 @@ impl TerminalState {
12 => "\x1b[24", 12 => "\x1b[24",
_ => bail!("unhandled fkey number {}", n), _ => bail!("unhandled fkey number {}", n),
}; };
write!(buf, "{};{}~", intro, 1 + mods.bits())?; write!(buf, "{};{}~", intro, 1 + encode_modifiers(mods))?;
buf.as_str() buf.as_str()
} }
} }

View File

@ -458,9 +458,21 @@ impl InputParser {
fn build_basic_key_map() -> KeyMap<InputEvent> { fn build_basic_key_map() -> KeyMap<InputEvent> {
let mut map = KeyMap::new(); let mut map = KeyMap::new();
let modifier_combos = &[
("", Modifiers::NONE),
(";1", Modifiers::NONE),
(";2", Modifiers::SHIFT),
(";3", Modifiers::ALT),
(";4", Modifiers::ALT | Modifiers::SHIFT),
(";5", Modifiers::CTRL),
(";6", Modifiers::CTRL | Modifiers::SHIFT),
(";7", Modifiers::CTRL | Modifiers::ALT),
(";8", Modifiers::CTRL | Modifiers::ALT | Modifiers::SHIFT),
];
for alpha in b'A'..=b'Z' { for alpha in b'A'..=b'Z' {
// Ctrl-[A..=Z] are sent as 1..=26 // Ctrl-[A..=Z] are sent as 1..=26
let ctrl = [alpha - 0x40]; let ctrl = [alpha & 0x1f];
map.insert( map.insert(
&ctrl, &ctrl,
InputEvent::Key(KeyEvent { InputEvent::Key(KeyEvent {
@ -480,6 +492,21 @@ impl InputParser {
); );
} }
// `CSI u` encodings for the ascii range;
// see http://www.leonerd.org.uk/hacks/fixterms/
for c in 0..=0x7fu8 {
for (suffix, modifiers) in modifier_combos {
let key = format!("\x1b[{}{}u", c, suffix);
map.insert(
key,
InputEvent::Key(KeyEvent {
key: KeyCode::Char(c as char),
modifiers: *modifiers,
}),
);
}
}
// Common arrow keys // Common arrow keys
for (keycode, dir) in &[ for (keycode, dir) in &[
(KeyCode::UpArrow, b'A'), (KeyCode::UpArrow, b'A'),
@ -499,16 +526,7 @@ impl InputParser {
}), }),
); );
// TODO: check compat; this happens to match up to iterm in my setup for (suffix, modifiers) in modifier_combos {
for (suffix, modifiers) in &[
(";2", Modifiers::SHIFT),
(";3", Modifiers::ALT),
(";4", Modifiers::ALT | Modifiers::SHIFT),
(";5", Modifiers::CTRL),
(";6", Modifiers::CTRL | Modifiers::SHIFT),
(";7", Modifiers::CTRL | Modifiers::ALT),
(";8", Modifiers::CTRL | Modifiers::ALT | Modifiers::SHIFT),
] {
let key = format!("\x1b[1{}{}", suffix, *dir as char); let key = format!("\x1b[1{}{}", suffix, *dir as char);
map.insert( map.insert(
key, key,
@ -535,6 +553,16 @@ impl InputParser {
modifiers: Modifiers::NONE, modifiers: Modifiers::NONE,
}), }),
); );
for (suffix, modifiers) in modifier_combos {
let key = format!("\x1bO1{}{}", suffix, *dir as char);
map.insert(
key,
InputEvent::Key(KeyEvent {
key: *keycode,
modifiers: *modifiers,
}),
);
}
} }
// Function keys 1-4 with no modifiers encoded using SS3 // Function keys 1-4 with no modifiers encoded using SS3
@ -556,16 +584,7 @@ impl InputParser {
// Function keys with modifiers encoded using CSI // Function keys with modifiers encoded using CSI
for n in 1..=12 { for n in 1..=12 {
for (suffix, modifiers) in &[ for (suffix, modifiers) in modifier_combos {
("", Modifiers::NONE),
(";2", Modifiers::SHIFT),
(";3", Modifiers::ALT),
(";4", Modifiers::ALT | Modifiers::SHIFT),
(";5", Modifiers::CTRL),
(";6", Modifiers::CTRL | Modifiers::SHIFT),
(";7", Modifiers::CTRL | Modifiers::ALT),
(";8", Modifiers::CTRL | Modifiers::ALT | Modifiers::SHIFT),
] {
let key = format!("\x1b[{code}{suffix}~", code = n + 10, suffix = suffix); let key = format!("\x1b[{code}{suffix}~", code = n + 10, suffix = suffix);
map.insert( map.insert(
key, key,

View File

@ -625,9 +625,16 @@ impl WindowView {
// sequences // sequences
extern "C" fn do_command_by_selector(this: &mut Object, _sel: Sel, a_selector: Sel) { extern "C" fn do_command_by_selector(this: &mut Object, _sel: Sel, a_selector: Sel) {
let selector = format!("{:?}", a_selector); let selector = format!("{:?}", a_selector);
let mut modifiers = Modifiers::default();
let key = match selector.as_ref() { let key = match selector.as_ref() {
"deleteBackward:" => KeyCode::Char('\x08'), "deleteBackward:" => KeyCode::Char('\x08'),
"deleteForward:" => KeyCode::Char('\x7f'), "deleteForward:" => KeyCode::Char('\x7f'),
"cancel:" => {
// FIXME: this isn't scalable to various keys
// and we lose eg: SHIFT if that is also pressed at the same time
modifiers = Modifiers::CTRL;
KeyCode::Char('\x1b')
}
"cancelOperation:" => KeyCode::Char('\x1b'), "cancelOperation:" => KeyCode::Char('\x1b'),
"insertNewline:" => KeyCode::Char('\r'), "insertNewline:" => KeyCode::Char('\r'),
"insertTab:" => KeyCode::Char('\t'), "insertTab:" => KeyCode::Char('\t'),
@ -648,7 +655,7 @@ impl WindowView {
let event = KeyEvent { let event = KeyEvent {
key, key,
raw_key: None, raw_key: None,
modifiers: Modifiers::default(), modifiers,
repeat_count: 1, repeat_count: 1,
key_is_down: true, key_is_down: true,
}; };
@ -888,6 +895,8 @@ impl WindowView {
let unmod = let unmod =
if virtual_key == super::keycodes::kVK_Delete && modifiers.contains(Modifiers::ALT) { if virtual_key == super::keycodes::kVK_Delete && modifiers.contains(Modifiers::ALT) {
"\x08" "\x08"
} else if virtual_key == super::keycodes::kVK_Tab {
"\t"
} else { } else {
unmod unmod
}; };
@ -974,11 +983,16 @@ impl WindowView {
} }
if let Some(key) = key_string_to_key_code(chars) { if let Some(key) = key_string_to_key_code(chars) {
let raw_key = if chars == unmod { let (key, raw_key) = if chars == unmod {
None (key, None)
} else { } else {
key_string_to_key_code(unmod) let raw = key_string_to_key_code(unmod)
.map(|kc| normalize_shifted_unmodified_key(kc, virtual_key)) .map(|kc| normalize_shifted_unmodified_key(kc, virtual_key));
match (&key, &raw) {
(KeyCode::Char(c), Some(raw)) if c.is_ascii_control() => (raw.clone(), None),
_ => (key, raw),
}
}; };
let event = KeyEvent { let event = KeyEvent {