Skip to main content

ai_agent/interact/events/
input_event.rs

1// Source: ~/claudecode/openclaudecode/src/ink/events/input-event.ts
2
3use super::event::Event;
4
5/// Keyboard key representation with all modifier flags and special key indicators.
6#[derive(Debug, Clone, Default)]
7pub struct Key {
8    pub up_arrow: bool,
9    pub down_arrow: bool,
10    pub left_arrow: bool,
11    pub right_arrow: bool,
12    pub page_down: bool,
13    pub page_up: bool,
14    pub wheel_up: bool,
15    pub wheel_down: bool,
16    pub home: bool,
17    pub end: bool,
18    pub return_key: bool,
19    pub escape: bool,
20    pub ctrl: bool,
21    pub shift: bool,
22    pub fn_key: bool,
23    pub tab: bool,
24    pub backspace: bool,
25    pub delete: bool,
26    pub meta: bool,
27    pub super_key: bool,
28}
29
30impl Key {
31    pub fn new() -> Self {
32        Self::default()
33    }
34
35    /// Returns true if any navigation key (arrows, page up/down, home, end) is pressed.
36    pub fn is_navigation(&self) -> bool {
37        self.up_arrow
38            || self.down_arrow
39            || self.left_arrow
40            || self.right_arrow
41            || self.page_down
42            || self.page_up
43            || self.home
44            || self.end
45    }
46
47    /// Returns true if any modifier key (ctrl, shift, alt/meta, super) is pressed.
48    pub fn has_modifier(&self) -> bool {
49        self.ctrl || self.shift || self.meta || self.super_key
50    }
51}
52
53/// Input event containing parsed keypress information.
54#[derive(Debug, Clone)]
55pub struct InputEvent {
56    pub keypress: ParsedKey,
57    pub key: Key,
58    pub input: String,
59    base: Event,
60}
61
62impl InputEvent {
63    pub fn new(keypress: ParsedKey) -> Self {
64        let key = parse_key(&keypress);
65        let input = compute_input(&keypress);
66
67        Self {
68            keypress,
69            key,
70            input,
71            base: Event::new(),
72        }
73    }
74
75    pub fn did_stop_immediate_propagation(&self) -> bool {
76        self.base.did_stop_immediate_propagation()
77    }
78
79    pub fn stop_immediate_propagation(&self) {
80        self.base.stop_immediate_propagation();
81    }
82}
83
84/// A parsed keypress from terminal input.
85#[derive(Debug, Clone)]
86pub struct ParsedKey {
87    pub kind: &'static str,
88    pub name: Option<String>,
89    pub fn_key: bool,
90    pub ctrl: bool,
91    pub meta: bool,
92    pub shift: bool,
93    pub option: bool,
94    pub super_key: bool,
95    pub sequence: Option<String>,
96    pub raw: Option<String>,
97    pub code: Option<String>,
98    pub is_pasted: bool,
99}
100
101impl ParsedKey {
102    pub fn new() -> Self {
103        Self {
104            kind: "key",
105            name: None,
106            fn_key: false,
107            ctrl: false,
108            meta: false,
109            shift: false,
110            option: false,
111            super_key: false,
112            sequence: None,
113            raw: None,
114            code: None,
115            is_pasted: false,
116        }
117    }
118}
119
120impl Default for ParsedKey {
121    fn default() -> Self {
122        Self::new()
123    }
124}
125
126/// Non-alphanumeric key names that should clear input.
127const NON_ALPHANUMERIC_KEYS: &[&str] = &[
128    "f1",
129    "f2",
130    "f3",
131    "f4",
132    "f5",
133    "f6",
134    "f7",
135    "f8",
136    "f9",
137    "f10",
138    "f11",
139    "f12",
140    "up",
141    "down",
142    "left",
143    "right",
144    "clear",
145    "end",
146    "home",
147    "insert",
148    "delete",
149    "pageup",
150    "pagedown",
151    "escape",
152    "backspace",
153    "wheelup",
154    "wheeldown",
155    "mouse",
156];
157
158fn parse_key(keypress: &ParsedKey) -> Key {
159    let mut key = Key::new();
160
161    if let Some(ref name) = keypress.name {
162        key.up_arrow = name == "up";
163        key.down_arrow = name == "down";
164        key.left_arrow = name == "left";
165        key.right_arrow = name == "right";
166        key.page_down = name == "pagedown";
167        key.page_up = name == "pageup";
168        key.wheel_up = name == "wheelup";
169        key.wheel_down = name == "wheeldown";
170        key.home = name == "home";
171        key.end = name == "end";
172        key.return_key = name == "return";
173        key.escape = name == "escape";
174        key.fn_key = keypress.fn_key;
175        key.ctrl = keypress.ctrl;
176        key.shift = keypress.shift;
177        key.tab = name == "tab";
178        key.backspace = name == "backspace";
179        key.delete = name == "delete";
180        key.meta = keypress.meta || name == "escape" || keypress.option;
181        key.super_key = keypress.super_key;
182    }
183
184    key
185}
186
187fn compute_input(keypress: &ParsedKey) -> String {
188    // When ctrl is set, use key name for control characters
189    if keypress.ctrl {
190        if let Some(ref name) = keypress.name {
191            if name == "space" {
192                return " ".to_string();
193            }
194            // Control characters: ctrl+a through ctrl+z map to ASCII 1-26
195            if name.len() == 1 {
196                let c = name.chars().next().unwrap();
197                if c.is_ascii_lowercase() {
198                    return ((c as u8 - b'a' + 1) as char).to_string();
199                }
200            }
201        }
202    }
203
204    // Handle sequence input
205    if let Some(ref seq) = keypress.sequence {
206        // Handle escape sequences
207        if seq.starts_with('\u{1b}') {
208            // Strip leading ESC
209            return seq[1..].to_string();
210        }
211        return seq.clone();
212    }
213
214    String::new()
215}