Skip to main content

escriba_input/
lib.rs

1//! `escriba-input` — translate madori input events into escriba's key
2//! abstraction.
3//!
4//! Two layers:
5//!   - [`translate_key`] takes a `madori::KeyEvent` (press only) and returns
6//!     the corresponding `escriba_keymap::Key` — Ctrl/Alt modifiers preserved,
7//!     printable chars passed through.
8//!   - [`EditorInput`] is the full frame-level dispatch — handles window
9//!     resizes, focus changes, IME, mouse events, and key events uniformly.
10
11use escriba_keymap::Key;
12use madori::AppEvent;
13use madori::event::{KeyCode, KeyEvent};
14
15/// Translate a single `madori::KeyEvent` into an `escriba_keymap::Key`.
16///
17/// Returns `None` for non-printable keys with no match (modifiers-only
18/// presses, unknown keycodes, releases).
19#[must_use]
20pub fn translate_key(event: &KeyEvent) -> Option<Key> {
21    if !event.pressed {
22        return None;
23    }
24    let mods = event.modifiers;
25    let key = match event.key {
26        KeyCode::Enter => Key::Enter,
27        KeyCode::Escape => Key::Esc,
28        KeyCode::Backspace => Key::Backspace,
29        KeyCode::Tab => Key::Tab,
30        KeyCode::Up => Key::Up,
31        KeyCode::Down => Key::Down,
32        KeyCode::Left => Key::Left,
33        KeyCode::Right => Key::Right,
34        KeyCode::Home => Key::Home,
35        KeyCode::End => Key::End,
36        KeyCode::PageUp => Key::PageUp,
37        KeyCode::PageDown => Key::PageDown,
38        KeyCode::Space => Key::Char(' '),
39        KeyCode::Char(c) => {
40            if mods.ctrl && !mods.alt {
41                return Some(Key::Ctrl(c.to_ascii_lowercase()));
42            }
43            if mods.alt && !mods.ctrl {
44                return Some(Key::Alt(c.to_ascii_lowercase()));
45            }
46            Key::Char(c)
47        }
48        KeyCode::Delete | KeyCode::F(_) | KeyCode::Unknown => return None,
49    };
50    Some(key)
51}
52
53/// What the runtime should do after translating an event.
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub enum InputOutcome {
56    /// A fully-translated escriba key.
57    Key(Key),
58    /// Window was resized.
59    Resized { width: u32, height: u32 },
60    /// Focus changed.
61    Focus(bool),
62    /// Exit requested (Cmd-Q / close button).
63    Quit,
64    /// Nothing translatable — ignore.
65    None,
66}
67
68#[must_use]
69pub fn translate_app_event(event: &AppEvent) -> InputOutcome {
70    match event {
71        AppEvent::Key(ke) => translate_key(ke).map_or(InputOutcome::None, InputOutcome::Key),
72        AppEvent::Resized { width, height } => InputOutcome::Resized {
73            width: *width,
74            height: *height,
75        },
76        AppEvent::Focused(f) => InputOutcome::Focus(*f),
77        AppEvent::CloseRequested => InputOutcome::Quit,
78        // IME / Mouse / RedrawRequested — phase 1 doesn't route them.
79        _ => InputOutcome::None,
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use madori::event::Modifiers;
87
88    fn key(kc: KeyCode, mods: Modifiers) -> KeyEvent {
89        KeyEvent {
90            key: kc,
91            pressed: true,
92            modifiers: mods,
93            text: None,
94        }
95    }
96
97    #[test]
98    fn char_without_modifiers() {
99        let k = translate_key(&key(KeyCode::Char('h'), Modifiers::default())).unwrap();
100        assert_eq!(k, Key::Char('h'));
101    }
102
103    #[test]
104    fn ctrl_char_becomes_ctrl_key() {
105        let mods = Modifiers {
106            ctrl: true,
107            ..Modifiers::default()
108        };
109        let k = translate_key(&key(KeyCode::Char('R'), mods)).unwrap();
110        assert_eq!(k, Key::Ctrl('r'));
111    }
112
113    #[test]
114    fn alt_char_becomes_alt_key() {
115        let mods = Modifiers {
116            alt: true,
117            ..Modifiers::default()
118        };
119        let k = translate_key(&key(KeyCode::Char('f'), mods)).unwrap();
120        assert_eq!(k, Key::Alt('f'));
121    }
122
123    #[test]
124    fn named_keys_map() {
125        assert_eq!(
126            translate_key(&key(KeyCode::Escape, Modifiers::default())).unwrap(),
127            Key::Esc
128        );
129        assert_eq!(
130            translate_key(&key(KeyCode::Enter, Modifiers::default())).unwrap(),
131            Key::Enter
132        );
133        assert_eq!(
134            translate_key(&key(KeyCode::Space, Modifiers::default())).unwrap(),
135            Key::Char(' ')
136        );
137    }
138
139    #[test]
140    fn release_returns_none() {
141        let mut e = key(KeyCode::Char('a'), Modifiers::default());
142        e.pressed = false;
143        assert!(translate_key(&e).is_none());
144    }
145
146    #[test]
147    fn close_event_quits() {
148        assert_eq!(
149            translate_app_event(&AppEvent::CloseRequested),
150            InputOutcome::Quit
151        );
152    }
153}