1
1
mirror of https://github.com/wez/wezterm.git synced 2024-09-20 03:09:06 +03:00

Add preliminary support for xterm's modifyOtherKeys encoding

see: https://invisible-island.net/xterm/modified-keys.html

I wouldn't be surprised if I've missed some cases, but this seems
to work for the keys mentioned in this issue:

refs: https://github.com/wez/wezterm/issues/2527
This commit is contained in:
Wez Furlong 2022-09-18 07:54:13 -07:00
parent e2c658e598
commit 47f4987687
4 changed files with 226 additions and 23 deletions

View File

@ -24,6 +24,7 @@ As features stabilize some brief notes about them will accumulate here.
[strikethrough_position](config/lua/config/strikethrough_position.md) options
to fine tune appearance. [#2505](https://github.com/wez/wezterm/issues/2505)
[#2326](https://github.com/wez/wezterm/issues/2326)
* Preliminary support for `modifyOtherKeys` keyboard encoding [#2527](https://github.com/wez/wezterm/issues/2527)
#### Fixed
* Wayland: key repeat gets stuck after pressing two keys in quick succession.

View File

@ -35,6 +35,7 @@ impl TerminalState {
encoding,
newline_mode: self.newline_mode,
application_cursor_keys: self.application_cursor_keys,
modify_other_keys: self.modify_other_keys,
},
is_down,
)?;

View File

@ -14,7 +14,7 @@ use termwiz::cell::UnicodeVersion;
use termwiz::escape::csi::{
Cursor, CursorStyle, DecPrivateMode, DecPrivateModeCode, Device, Edit, EraseInDisplay,
EraseInLine, Mode, Sgr, TabulationClear, TerminalMode, TerminalModeCode, Window, XtSmGraphics,
XtSmGraphicsAction, XtSmGraphicsItem, XtSmGraphicsStatus,
XtSmGraphicsAction, XtSmGraphicsItem, XtSmGraphicsStatus, XtermKeyModifierResource,
};
use termwiz::escape::{OneBased, OperatingSystemCommand, CSI};
use termwiz::image::ImageData;
@ -285,6 +285,7 @@ pub struct TerminalState {
/// designated as cursor keys. This includes various navigation
/// keys. The code in key_down() is responsible for interpreting this.
application_cursor_keys: bool,
modify_other_keys: Option<i64>,
dec_ansi_mode: bool,
@ -522,6 +523,7 @@ impl TerminalState {
dec_origin_mode: false,
insert: false,
application_cursor_keys: false,
modify_other_keys: None,
dec_ansi_mode: false,
sixel_display_mode: false,
use_private_color_registers_for_each_graphic: false,
@ -1206,6 +1208,7 @@ impl TerminalState {
// setting for dec_auto_wrap, so we do too
self.dec_auto_wrap = true;
self.application_cursor_keys = false;
self.modify_other_keys = None;
self.application_keypad = false;
self.top_and_bottom_margins = 0..self.screen().physical_rows as i64;
self.left_and_right_margins = 0..self.screen().physical_cols;
@ -1808,6 +1811,17 @@ impl TerminalState {
log::warn!("unhandled {:?}", mode);
}
Mode::XtermKeyMode {
resource: XtermKeyModifierResource::OtherKeys,
value,
} => {
self.modify_other_keys = match value {
Some(0) => None,
_ => value,
};
log::debug!("XtermKeyMode OtherKeys -> {:?}", self.modify_other_keys);
}
Mode::XtermKeyMode { resource, value } => {
log::warn!("unhandled XtermKeyMode {:?} {:?}", resource, value);
}

View File

@ -33,6 +33,7 @@ pub struct KeyCodeEncodeModes {
pub encoding: KeyboardEncoding,
pub application_cursor_keys: bool,
pub newline_mode: bool,
pub modify_other_keys: Option<i64>,
}
#[cfg(windows)]
@ -506,12 +507,15 @@ impl KeyCode {
&& mods.contains(Modifiers::CTRL)
&& modes.encoding == KeyboardEncoding::CsiU =>
{
csi_u_encode(&mut buf, c, mods, modes.encoding)?;
csi_u_encode(&mut buf, c, mods, &modes)?;
}
Char(c) if c.is_ascii_uppercase() && mods.contains(Modifiers::CTRL) => {
csi_u_encode(&mut buf, c, mods, modes.encoding)?;
csi_u_encode(&mut buf, c, mods, &modes)?;
}
Char(c) if mods.contains(Modifiers::CTRL) && modes.modify_other_keys == Some(2) => {
csi_u_encode(&mut buf, c, mods, &modes)?;
}
Char(c) if mods.contains(Modifiers::CTRL) && ctrl_mapping(c).is_some() => {
let c = ctrl_mapping(c).unwrap();
if mods.contains(Modifiers::ALT) {
@ -533,17 +537,30 @@ impl KeyCode {
buf.push(c);
}
Enter | Escape | Backspace => {
Backspace => {
// Backspace sends the default VERASE which is confusingly
// the DEL ascii codepoint rather than BS.
// We only send BS when CTRL is held.
if mods.contains(Modifiers::CTRL) {
csi_u_encode(&mut buf, '\x08', mods, &modes)?;
} else if mods.contains(Modifiers::SHIFT) {
csi_u_encode(&mut buf, '\x7f', mods, &modes)?;
} else {
if mods.contains(Modifiers::ALT) {
buf.push(0x1b as char);
}
buf.push('\x7f');
}
}
Enter | Escape => {
let c = match key {
Enter => '\r',
Escape => '\x1b',
// Backspace sends the default VERASE which is confusingly
// the DEL ascii codepoint
Backspace => '\x7f',
_ => unreachable!(),
};
if mods.contains(Modifiers::SHIFT) || mods.contains(Modifiers::CTRL) {
csi_u_encode(&mut buf, c, mods, modes.encoding)?;
csi_u_encode(&mut buf, c, mods, &modes)?;
} else {
if mods.contains(Modifiers::ALT) {
buf.push(0x1b as char);
@ -555,6 +572,10 @@ impl KeyCode {
}
}
Tab if !mods.is_empty() && modes.modify_other_keys.is_some() => {
csi_u_encode(&mut buf, '\t', mods, &modes)?;
}
Tab => {
if mods.contains(Modifiers::ALT) {
buf.push(0x1b as char);
@ -575,7 +596,7 @@ impl KeyCode {
if mods.is_empty() {
buf.push(c);
} else {
csi_u_encode(&mut buf, c, mods, modes.encoding)?;
csi_u_encode(&mut buf, c, mods, &modes)?;
}
}
@ -800,21 +821,34 @@ fn csi_u_encode(
buf: &mut String,
c: char,
mods: Modifiers,
encoding: KeyboardEncoding,
modes: &KeyCodeEncodeModes,
) -> Result<()> {
if encoding == KeyboardEncoding::CsiU && is_ascii(c) {
if modes.encoding == KeyboardEncoding::CsiU && is_ascii(c) {
write!(buf, "\x1b[{};{}u", c as u32, 1 + encode_modifiers(mods))?;
} else {
let c = if mods.contains(Modifiers::CTRL) && ctrl_mapping(c).is_some() {
ctrl_mapping(c).unwrap()
} else {
c
};
if mods.contains(Modifiers::ALT) {
buf.push(0x1b as char);
}
write!(buf, "{}", c)?;
return Ok(());
}
// <https://invisible-island.net/xterm/modified-keys.html>
match (c, modes.modify_other_keys) {
('c' | 'd' | '\x1b' | '\x7f' | '\x08', Some(1)) => {
// Exclude well-known keys from modifyOtherKeys mode 1
}
(c, Some(_)) => {
write!(buf, "\x1b[27;{};{}~", 1 + encode_modifiers(mods), c as u32)?;
return Ok(());
}
_ => {}
}
let c = if mods.contains(Modifiers::CTRL) && ctrl_mapping(c).is_some() {
ctrl_mapping(c).unwrap()
} else {
c
};
if mods.contains(Modifiers::ALT) {
buf.push(0x1b as char);
}
write!(buf, "{}", c)?;
Ok(())
}
@ -1149,10 +1183,10 @@ 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 {
// `CSI u` encodings for the ascii range;
// see http://www.leonerd.org.uk/hacks/fixterms/
let key = format!("\x1b[{}{}u", c, suffix);
map.insert(
key,
@ -1161,6 +1195,24 @@ impl InputParser {
modifiers: *modifiers,
}),
);
if !suffix.is_empty() {
// xterm modifyOtherKeys sequences
let key = format!("\x1b[27{};{}~", suffix, c);
map.insert(
key,
InputEvent::Key(KeyEvent {
key: match c {
8 | 0x7f => KeyCode::Backspace,
0x1b => KeyCode::Escape,
9 => KeyCode::Tab,
10 | 13 => KeyCode::Enter,
_ => KeyCode::Char(c as char),
},
modifiers: *modifiers,
}),
);
}
}
}
@ -1771,12 +1823,147 @@ mod test {
);
}
#[test]
fn modify_other_keys_parse() {
let mut p = InputParser::new();
let inputs =
p.parse_as_vec(b"\x1b[27;5;13~\x1b[27;5;9~\x1b[27;6;8~\x1b[27;2;127~\x1b[27;6;27~");
assert_eq!(
vec![
InputEvent::Key(KeyEvent {
key: KeyCode::Enter,
modifiers: Modifiers::CTRL,
}),
InputEvent::Key(KeyEvent {
key: KeyCode::Tab,
modifiers: Modifiers::CTRL,
}),
InputEvent::Key(KeyEvent {
key: KeyCode::Backspace,
modifiers: Modifiers::CTRL | Modifiers::SHIFT,
}),
InputEvent::Key(KeyEvent {
key: KeyCode::Backspace,
modifiers: Modifiers::SHIFT,
}),
InputEvent::Key(KeyEvent {
key: KeyCode::Escape,
modifiers: Modifiers::CTRL | Modifiers::SHIFT,
}),
],
inputs
);
}
#[test]
fn modify_other_keys_encode() {
let mode = KeyCodeEncodeModes {
encoding: KeyboardEncoding::Xterm,
newline_mode: false,
application_cursor_keys: false,
modify_other_keys: None,
};
let mode_1 = KeyCodeEncodeModes {
encoding: KeyboardEncoding::Xterm,
newline_mode: false,
application_cursor_keys: false,
modify_other_keys: Some(1),
};
let mode_2 = KeyCodeEncodeModes {
encoding: KeyboardEncoding::Xterm,
newline_mode: false,
application_cursor_keys: false,
modify_other_keys: Some(2),
};
assert_eq!(
KeyCode::Enter.encode(Modifiers::CTRL, mode, true).unwrap(),
"\r".to_string()
);
assert_eq!(
KeyCode::Enter
.encode(Modifiers::CTRL, mode_1, true)
.unwrap(),
"\x1b[27;5;13~".to_string()
);
assert_eq!(
KeyCode::Enter
.encode(Modifiers::CTRL | Modifiers::SHIFT, mode_1, true)
.unwrap(),
"\x1b[27;6;13~".to_string()
);
// This case is not conformant with xterm!
// xterm just returns tab for CTRL-Tab when modify_other_keys
// is not set.
assert_eq!(
KeyCode::Tab.encode(Modifiers::CTRL, mode, true).unwrap(),
"\x1b[9;5u".to_string()
);
assert_eq!(
KeyCode::Tab.encode(Modifiers::CTRL, mode_1, true).unwrap(),
"\x1b[27;5;9~".to_string()
);
assert_eq!(
KeyCode::Tab
.encode(Modifiers::CTRL | Modifiers::SHIFT, mode_1, true)
.unwrap(),
"\x1b[27;6;9~".to_string()
);
assert_eq!(
KeyCode::Char('c')
.encode(Modifiers::CTRL, mode, true)
.unwrap(),
"\x03".to_string()
);
assert_eq!(
KeyCode::Char('c')
.encode(Modifiers::CTRL, mode_1, true)
.unwrap(),
"\x03".to_string()
);
assert_eq!(
KeyCode::Char('c')
.encode(Modifiers::CTRL, mode_2, true)
.unwrap(),
"\x1b[27;5;99~".to_string()
);
assert_eq!(
KeyCode::Char('1')
.encode(Modifiers::CTRL, mode, true)
.unwrap(),
"1".to_string()
);
assert_eq!(
KeyCode::Char('1')
.encode(Modifiers::CTRL, mode_2, true)
.unwrap(),
"\x1b[27;5;49~".to_string()
);
assert_eq!(
KeyCode::Char(',')
.encode(Modifiers::CTRL, mode, true)
.unwrap(),
",".to_string()
);
assert_eq!(
KeyCode::Char(',')
.encode(Modifiers::CTRL, mode_2, true)
.unwrap(),
"\x1b[27;5;44~".to_string()
);
}
#[test]
fn encode_issue_892() {
let mode = KeyCodeEncodeModes {
encoding: KeyboardEncoding::Xterm,
newline_mode: false,
application_cursor_keys: false,
modify_other_keys: None,
};
assert_eq!(