aboutsummaryrefslogtreecommitdiff
path: root/src/input
diff options
context:
space:
mode:
Diffstat (limited to 'src/input')
-rw-r--r--src/input/encode.rs71
-rw-r--r--src/input/model.rs21
-rw-r--r--src/input/parse.rs1
3 files changed, 86 insertions, 7 deletions
diff --git a/src/input/encode.rs b/src/input/encode.rs
index b10f8003..0ea011ed 100644
--- a/src/input/encode.rs
+++ b/src/input/encode.rs
@@ -1,10 +1,10 @@
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEventKind};
+use super::model::KITTY_FLAG_REPORT_ALL_KEYS;
use super::{KeyboardProtocol, MouseProtocolEncoding, TerminalKey};
const KITTY_FLAG_REPORT_EVENT_TYPES: u16 = 0b0000_0010;
const KITTY_FLAG_REPORT_ALTERNATE_KEYS: u16 = 0b0000_0100;
-const KITTY_FLAG_REPORT_ALL_KEYS: u16 = 0b0000_1000;
/// Encode a key event for a PTY child using the pane's negotiated keyboard protocol.
#[allow(dead_code)] // exercised in input unit tests; production uses TerminalRuntime helpers
@@ -13,6 +13,10 @@ pub fn encode_key(key: KeyEvent, protocol: KeyboardProtocol) -> Vec<u8> {
}
pub fn encode_terminal_key(key: TerminalKey, protocol: KeyboardProtocol) -> Vec<u8> {
+ if key.is_text_commit {
+ return encode_text_input(&key).unwrap_or_default();
+ }
+
// A release event only produces bytes when the pane protocol reports event
// types (Kitty REPORT_EVENT_TYPES). Otherwise the child expects a single
// legacy byte per keystroke, so re-emitting it on release would double keys
@@ -22,13 +26,26 @@ pub fn encode_terminal_key(key: TerminalKey, protocol: KeyboardProtocol) -> Vec<
return Vec::new();
}
+ let kitty_first = protocol.reports_all_keys()
+ || (key.kind == crossterm::event::KeyEventKind::Release && protocol.reports_event_types());
+
+ if kitty_first {
+ if let KeyboardProtocol::Kitty { flags } = protocol {
+ if let Some(bytes) = try_encode_csi_u(&key, flags) {
+ return bytes;
+ }
+ }
+ }
+
if let Some(bytes) = encode_text_input(&key) {
return bytes;
}
- if let KeyboardProtocol::Kitty { flags } = protocol {
- if let Some(bytes) = try_encode_csi_u(&key, flags) {
- return bytes;
+ if !kitty_first {
+ if let KeyboardProtocol::Kitty { flags } = protocol {
+ if let Some(bytes) = try_encode_csi_u(&key, flags) {
+ return bytes;
+ }
}
}
if key.kind == crossterm::event::KeyEventKind::Release && protocol.reports_event_types() {
@@ -838,6 +855,43 @@ mod tests {
}
#[test]
+ fn kitty_report_all_keys_encodes_printable_event_kinds() {
+ for (kind, expected) in [
+ (
+ crossterm::event::KeyEventKind::Press,
+ b"\x1b[106;1:1u".as_slice(),
+ ),
+ (
+ crossterm::event::KeyEventKind::Repeat,
+ b"\x1b[106;1:2u".as_slice(),
+ ),
+ (
+ crossterm::event::KeyEventKind::Release,
+ b"\x1b[106;1:3u".as_slice(),
+ ),
+ ] {
+ let key = KeyEvent::new_with_kind(KeyCode::Char('j'), KeyModifiers::empty(), kind);
+ assert_eq!(
+ encode_key(key, KeyboardProtocol::Kitty { flags: 15 }),
+ expected
+ );
+ }
+ }
+
+ #[test]
+ fn kitty_printable_release_is_encoded_without_report_all() {
+ let release = KeyEvent::new_with_kind(
+ KeyCode::Char('j'),
+ KeyModifiers::empty(),
+ crossterm::event::KeyEventKind::Release,
+ );
+ assert_eq!(
+ encode_key(release, KeyboardProtocol::Kitty { flags: 3 }),
+ b"\x1b[106;1:3u"
+ );
+ }
+
+ #[test]
fn kitty_shift_tab() {
let key = KeyEvent::new(KeyCode::Tab, KeyModifiers::SHIFT);
assert_eq!(
@@ -869,13 +923,16 @@ mod tests {
}
#[test]
- fn kitty_shift_letter_release_does_not_emit_text() {
+ fn kitty_shift_letter_release_uses_csi_u() {
let key = KeyEvent::new_with_kind(
KeyCode::Char('L'),
KeyModifiers::SHIFT,
crossterm::event::KeyEventKind::Release,
);
- assert_eq!(encode_key(key, KeyboardProtocol::Kitty { flags: 7 }), b"");
+ assert_eq!(
+ encode_key(key, KeyboardProtocol::Kitty { flags: 7 }),
+ b"\x1b[108:76;2:3u"
+ );
}
#[test]
@@ -893,7 +950,7 @@ mod tests {
.with_kind(crossterm::event::KeyEventKind::Release);
assert_eq!(
encode_terminal_key(key, KeyboardProtocol::Kitty { flags: 7 }),
- b""
+ b"\x1b[63;2:3u"
);
}
diff --git a/src/input/model.rs b/src/input/model.rs
index a3e74056..f6072bc6 100644
--- a/src/input/model.rs
+++ b/src/input/model.rs
@@ -9,6 +9,7 @@ pub struct TerminalKey {
pub modifiers: KeyModifiers,
pub kind: crossterm::event::KeyEventKind,
pub shifted_codepoint: Option<u32>,
+ pub is_text_commit: bool,
}
impl TerminalKey {
@@ -18,6 +19,7 @@ impl TerminalKey {
modifiers,
kind: crossterm::event::KeyEventKind::Press,
shifted_codepoint: None,
+ is_text_commit: false,
}
}
@@ -32,6 +34,19 @@ impl TerminalKey {
self
}
+ pub fn as_text_commit(mut self) -> Self {
+ let has_text_only_modifiers = match self.code {
+ KeyCode::Char(ch) if ch.is_ascii_uppercase() => {
+ self.modifiers == KeyModifiers::SHIFT || self.modifiers.is_empty()
+ }
+ KeyCode::Char(_) => self.modifiers.is_empty(),
+ _ => false,
+ };
+ self.is_text_commit =
+ has_text_only_modifiers && self.kind == crossterm::event::KeyEventKind::Press;
+ self
+ }
+
pub fn as_key_event(self) -> KeyEvent {
KeyEvent::new_with_kind(self.code, self.modifiers, self.kind)
}
@@ -43,6 +58,8 @@ impl From<KeyEvent> for TerminalKey {
}
}
+pub(crate) const KITTY_FLAG_REPORT_ALL_KEYS: u16 = 0b0000_1000;
+
#[cfg(not(windows))]
pub fn ime_compatible_keyboard_enhancement_flags() -> KeyboardEnhancementFlags {
KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
@@ -117,6 +134,10 @@ impl KeyboardProtocol {
pub(crate) fn reports_event_types(self) -> bool {
matches!(self, Self::Kitty { flags } if flags & 0b0000_0010 != 0)
}
+
+ pub(crate) fn reports_all_keys(self) -> bool {
+ matches!(self, Self::Kitty { flags } if flags & KITTY_FLAG_REPORT_ALL_KEYS != 0)
+ }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
diff --git a/src/input/parse.rs b/src/input/parse.rs
index 0071596b..36de52e8 100644
--- a/src/input/parse.rs
+++ b/src/input/parse.rs
@@ -45,6 +45,7 @@ fn parse_kitty_key_sequence(data: &str) -> Option<TerminalKey> {
modifiers: key_modifiers_from_u8(modifier),
kind,
shifted_codepoint,
+ is_text_commit: false,
})
}