aboutsummaryrefslogtreecommitdiff
path: root/src/input
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-04-14 01:04:51 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-04-14 01:04:51 +0300
commitedaa3a1a092763cb25fd9e21772c95ee56a75a15 (patch)
tree88d54f6a3aa2a43c1c6dd4c7aaabd78ddfc03466 /src/input
parent5ae1cea94bcd77c8a75abfbe6c996e5cf2a91ab7 (diff)
fix unify app key normalization for enhanced keyboard reporting
Diffstat (limited to 'src/input')
-rw-r--r--src/input/mod.rs5
-rw-r--r--src/input/model.rs52
2 files changed, 56 insertions, 1 deletions
diff --git a/src/input/mod.rs b/src/input/mod.rs
index 109fd312..67e8a14d 100644
--- a/src/input/mod.rs
+++ b/src/input/mod.rs
@@ -8,7 +8,10 @@ pub use encode::encode_key;
pub use encode::{
encode_cursor_key, encode_mouse_button, encode_mouse_scroll, encode_terminal_key,
};
-pub use model::{KeyboardProtocol, MouseProtocolEncoding, MouseProtocolMode, TerminalKey};
+pub use model::{
+ normalize_app_key_binding, KeyboardProtocol, MouseProtocolEncoding, MouseProtocolMode,
+ TerminalKey,
+};
pub use parse::parse_terminal_key_sequence;
#[cfg(test)]
diff --git a/src/input/model.rs b/src/input/model.rs
index 9339b965..5721f82b 100644
--- a/src/input/model.rs
+++ b/src/input/model.rs
@@ -1,5 +1,57 @@
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
+pub fn normalize_app_key_binding(
+ code: KeyCode,
+ modifiers: KeyModifiers,
+ shifted_codepoint: Option<u32>,
+) -> (KeyCode, KeyModifiers) {
+ let KeyCode::Char(raw_char) = code else {
+ return (code, modifiers);
+ };
+
+ let shifted_char = shifted_codepoint
+ .and_then(char::from_u32)
+ .or_else(|| modifiers.contains(KeyModifiers::SHIFT).then_some(raw_char))
+ .and_then(shifted_printable_char);
+
+ let normalized_char = shifted_char.unwrap_or(raw_char);
+ let mut normalized_modifiers = modifiers;
+ if shifted_char.is_some() {
+ normalized_modifiers.remove(KeyModifiers::SHIFT);
+ }
+
+ (KeyCode::Char(normalized_char), normalized_modifiers)
+}
+
+fn shifted_printable_char(ch: char) -> Option<char> {
+ match ch {
+ 'a'..='z' => Some(ch.to_ascii_uppercase()),
+ 'A'..='Z' => Some(ch),
+ '`' | '~' => Some('~'),
+ '1' | '!' => Some('!'),
+ '2' | '@' => Some('@'),
+ '3' | '#' => Some('#'),
+ '4' | '$' => Some('$'),
+ '5' | '%' => Some('%'),
+ '6' | '^' => Some('^'),
+ '7' | '&' => Some('&'),
+ '8' | '*' => Some('*'),
+ '9' | '(' => Some('('),
+ '0' | ')' => Some(')'),
+ '-' | '_' => Some('_'),
+ '=' | '+' => Some('+'),
+ '[' | '{' => Some('{'),
+ ']' | '}' => Some('}'),
+ '\\' | '|' => Some('|'),
+ ';' | ':' => Some(':'),
+ '\'' | '"' => Some('"'),
+ ',' | '<' => Some('<'),
+ '.' | '>' => Some('>'),
+ '/' | '?' => Some('?'),
+ _ => None,
+ }
+}
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TerminalKey {
pub code: KeyCode,