aboutsummaryrefslogtreecommitdiff
path: root/src/raw_input.rs
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-05-22 15:18:08 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-05-22 15:18:08 +0300
commit29fd06747b31e8ec7fbf32af662263040783b6c7 (patch)
treef8dc493d02d15129ff22e1dcc6b8bc7350a95731 /src/raw_input.rs
parent3c7c4464e5d46cf28036edbd98450baae768b823 (diff)
fix: accept unicode keybindings
refs #247
Diffstat (limited to 'src/raw_input.rs')
-rw-r--r--src/raw_input.rs47
1 files changed, 46 insertions, 1 deletions
diff --git a/src/raw_input.rs b/src/raw_input.rs
index fa496b95..d75c6c49 100644
--- a/src/raw_input.rs
+++ b/src/raw_input.rs
@@ -231,6 +231,11 @@ pub(crate) fn flush_incomplete_input_bytes(buffer: &mut Vec<u8>) -> Option<Vec<u
return None;
}
+ if buffer.first() == Some(&ESC) && starts_with_incomplete_utf8_char(&buffer[1..]) {
+ tracing::trace!(bytes = ?buffer, "waiting for escaped UTF-8 continuation bytes");
+ return None;
+ }
+
tracing::debug!(bytes = ?buffer, "dropping incomplete raw input buffer after timeout");
buffer.clear();
None
@@ -388,7 +393,12 @@ fn complete_escape_sequence_len(buffer: &[u8]) -> Option<usize> {
return (buffer.len() >= 3).then_some(3);
}
- Some(2)
+ let escaped_char_width = utf8_char_width(buffer[1])?;
+ if buffer.len() < 1 + escaped_char_width {
+ return None;
+ }
+ std::str::from_utf8(&buffer[1..1 + escaped_char_width]).ok()?;
+ Some(1 + escaped_char_width)
}
fn find_osc_terminator(buffer: &[u8]) -> Option<usize> {
@@ -1046,6 +1056,41 @@ mod tests {
}
#[test]
+ fn alt_utf8_char_drains_as_one_event_before_following_input() {
+ let events = parse_raw_input_bytes_sync("\x1béx".as_bytes());
+ assert_eq!(events.len(), 2);
+ assert_raw_key(
+ events.into_iter().next().unwrap(),
+ KeyCode::Char('é'),
+ KeyModifiers::ALT,
+ );
+ }
+
+ #[test]
+ fn chunked_alt_utf8_waits_for_continuation_byte_after_escape() {
+ let (tx, mut rx) = mpsc::channel(8);
+ let mut buffer = Vec::new();
+ let bytes = "\x1bé".as_bytes();
+
+ drain_chunk(&mut buffer, &tx, &bytes[..2]);
+ assert_eq!(buffer, bytes[..2]);
+ assert!(collect_events(&mut rx).is_empty());
+ flush_incomplete_buffer(&mut buffer, &tx);
+ assert_eq!(buffer, bytes[..2]);
+ assert!(collect_events(&mut rx).is_empty());
+
+ drain_chunk(&mut buffer, &tx, &bytes[2..]);
+ assert!(buffer.is_empty());
+ let events = collect_events(&mut rx);
+ assert_eq!(events.len(), 1);
+ assert_raw_key(
+ events.into_iter().next().unwrap(),
+ KeyCode::Char('é'),
+ KeyModifiers::ALT,
+ );
+ }
+
+ #[test]
fn chunked_utf8_waits_for_continuation_byte() {
let (tx, mut rx) = mpsc::channel(8);
let mut buffer = Vec::new();