Skip to main content

shrub_core/sys/unix/
input.rs

1//! Terminal input utilities
2
3use std::collections::HashMap;
4use std::sync::LazyLock;
5
6use terminfo::capability as cap;
7
8use crate::{error, interfaces};
9
10macro_rules! key {
11    ( $terminfo:expr , $our_key:expr, $terminfo_key:ty ) => {{
12        (
13            $our_key,
14            $terminfo
15                .get::<$terminfo_key>()
16                .map(|k| k.expand().to_vec()),
17        )
18    }};
19}
20
21fn build_terminfo_key_map() -> HashMap<Vec<u8>, interfaces::Key> {
22    let mut map: HashMap<Vec<u8>, interfaces::Key> = HashMap::new();
23
24    if let Ok(ti) = terminfo::Database::from_env() {
25        // Iterate over key capabilities and populate the map
26        let key_capabilities = [
27            key!(ti, interfaces::Key::F(1), cap::KeyF1<'_>),
28            key!(ti, interfaces::Key::F(2), cap::KeyF2<'_>),
29            key!(ti, interfaces::Key::F(3), cap::KeyF3<'_>),
30            key!(ti, interfaces::Key::F(4), cap::KeyF4<'_>),
31            key!(ti, interfaces::Key::F(5), cap::KeyF5<'_>),
32            key!(ti, interfaces::Key::F(6), cap::KeyF6<'_>),
33            key!(ti, interfaces::Key::F(7), cap::KeyF7<'_>),
34            key!(ti, interfaces::Key::F(8), cap::KeyF8<'_>),
35            key!(ti, interfaces::Key::F(9), cap::KeyF9<'_>),
36            key!(ti, interfaces::Key::F(10), cap::KeyF10<'_>),
37            key!(ti, interfaces::Key::F(11), cap::KeyF11<'_>),
38            key!(ti, interfaces::Key::F(12), cap::KeyF12<'_>),
39            key!(ti, interfaces::Key::Backspace, cap::KeyBackspace<'_>),
40            key!(ti, interfaces::Key::Enter, cap::KeyEnter<'_>),
41            key!(ti, interfaces::Key::Left, cap::KeyLeft<'_>),
42            key!(ti, interfaces::Key::Right, cap::KeyRight<'_>),
43            key!(ti, interfaces::Key::Up, cap::KeyUp<'_>),
44            key!(ti, interfaces::Key::Down, cap::KeyDown<'_>),
45            key!(ti, interfaces::Key::Home, cap::KeyHome<'_>),
46            key!(ti, interfaces::Key::End, cap::KeyEnd<'_>),
47            key!(ti, interfaces::Key::PageUp, cap::KeyPPage<'_>),
48            key!(ti, interfaces::Key::PageDown, cap::KeyNPage<'_>),
49            key!(ti, interfaces::Key::BackTab, cap::BackTab<'_>),
50            // It's not clear if these belong here, because they're not
51            // strictly "key" capabilities.
52            key!(ti, interfaces::Key::Up, cap::CursorUp<'_>),
53            key!(ti, interfaces::Key::Down, cap::CursorDown<'_>),
54            key!(ti, interfaces::Key::Left, cap::CursorLeft<'_>),
55            key!(ti, interfaces::Key::Right, cap::CursorRight<'_>),
56        ];
57
58        for (key, v) in key_capabilities {
59            if let Some(Ok(v)) = v {
60                map.insert(v.clone(), key.clone());
61            }
62        }
63    }
64
65    map
66}
67
68pub(crate) static TERMINFO_KEY_MAP: LazyLock<HashMap<Vec<u8>, interfaces::Key>> =
69    LazyLock::new(build_terminfo_key_map);
70
71/// Translates a key code (byte sequence) into a `Key` enum value. Returns
72/// `None` if the key code is not recognized.
73///
74/// # Arguments
75///
76/// * `key_code`: The byte sequence representing the key code.
77pub fn try_get_key_from_key_code(key_code: &[u8]) -> Option<interfaces::Key> {
78    if let Some(key) = TERMINFO_KEY_MAP.get(key_code) {
79        Some(key.clone())
80    } else if key_code.len() == 1 && !key_code[0].is_ascii_control() {
81        Some(interfaces::Key::Character(key_code[0] as char))
82    } else {
83        None
84    }
85}