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:
parent
e2c658e598
commit
47f4987687
@ -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.
|
||||
|
@ -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,
|
||||
)?;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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!(
|
||||
|
Loading…
Reference in New Issue
Block a user