synd_term/keymap/
mod.rs

1use std::{collections::HashMap, ops::ControlFlow};
2
3use anyhow::{anyhow, bail};
4use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
5
6mod default;
7
8pub mod macros;
9
10use crate::{application::event::KeyEventResult, command::Command};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub(crate) enum KeymapId {
14    Global = 0,
15    Login = 1,
16    Tabs = 2,
17    Entries = 3,
18    Subscription = 4,
19    GhNotification = 5,
20    Filter = 6,
21    CategoryFiltering = 7,
22    UnsubscribePopupSelection = 8,
23    GhNotificationFilterPopup = 9,
24}
25
26#[derive(Debug)]
27pub(crate) struct Keymap {
28    #[allow(dead_code)]
29    id: KeymapId,
30    enable: bool,
31    trie: KeyTrie,
32    pending_keys: Vec<KeyEvent>,
33}
34
35impl Keymap {
36    /// Construct a `Keymap`
37    pub fn new(id: KeymapId, trie: KeyTrie) -> Self {
38        Self {
39            id,
40            enable: false,
41            trie,
42            pending_keys: Vec::with_capacity(2),
43        }
44    }
45
46    pub fn from_map(id: KeymapId, map: HashMap<KeyEvent, KeyTrie>) -> Self {
47        Self::new(id, KeyTrie::Node(KeyTrieNode { map }))
48    }
49
50    fn search(&mut self, event: &KeyEvent) -> Option<Command> {
51        let first = self.pending_keys.first().unwrap_or(event);
52        let trie = match self.trie.search(&[*first]) {
53            Some(KeyTrie::Command(cmd)) => return Some(cmd),
54            Some(KeyTrie::Node(node)) => KeyTrie::Node(node),
55            None => return None,
56        };
57
58        self.pending_keys.push(*event);
59        match trie.search(&self.pending_keys[1..]) {
60            Some(KeyTrie::Command(cmd)) => {
61                self.pending_keys.drain(..);
62                Some(cmd)
63            }
64            Some(KeyTrie::Node(_)) => None,
65            _ => {
66                self.pending_keys.drain(..);
67                None
68            }
69        }
70    }
71}
72
73pub(crate) struct KeymapsConfig {
74    pub login: KeyTrie,
75    pub tabs: KeyTrie,
76    pub entries: KeyTrie,
77    pub subscription: KeyTrie,
78    pub gh_notification: KeyTrie,
79    pub gh_notification_filter_popup: KeyTrie,
80    pub filter: KeyTrie,
81    pub unsubscribe_popup: KeyTrie,
82    pub global: KeyTrie,
83}
84
85impl Default for KeymapsConfig {
86    fn default() -> Self {
87        default::default()
88    }
89}
90
91#[derive(Debug)]
92pub(crate) struct Keymaps {
93    keymaps: Vec<Keymap>,
94}
95
96impl Keymaps {
97    pub fn new(config: KeymapsConfig) -> Self {
98        // order is matter
99        let keymaps = vec![
100            Keymap::new(KeymapId::Global, config.global),
101            Keymap::new(KeymapId::Login, config.login),
102            Keymap::new(KeymapId::Tabs, config.tabs),
103            Keymap::new(KeymapId::Entries, config.entries),
104            Keymap::new(KeymapId::Subscription, config.subscription),
105            Keymap::new(KeymapId::GhNotification, config.gh_notification),
106            Keymap::new(KeymapId::Filter, config.filter),
107            Keymap::new(KeymapId::CategoryFiltering, KeyTrie::default()),
108            Keymap::new(
109                KeymapId::UnsubscribePopupSelection,
110                config.unsubscribe_popup,
111            ),
112            Keymap::new(
113                KeymapId::GhNotificationFilterPopup,
114                config.gh_notification_filter_popup,
115            ),
116        ];
117
118        Self { keymaps }
119    }
120
121    pub fn enable(&mut self, id: KeymapId) -> &mut Self {
122        self.keymaps[id as usize].enable = true;
123        self
124    }
125
126    pub fn disable(&mut self, id: KeymapId) -> &mut Self {
127        self.keymaps[id as usize].enable = false;
128        self
129    }
130
131    pub fn update(&mut self, id: KeymapId, keymap: Keymap) {
132        let mut keymap = keymap;
133        keymap.enable = true;
134        self.keymaps[id as usize] = keymap;
135    }
136
137    pub fn search(&mut self, event: &KeyEvent) -> KeyEventResult {
138        match self
139            .keymaps
140            .iter_mut()
141            .rev()
142            .filter(|k| k.enable)
143            .try_for_each(|keymap| match keymap.search(event) {
144                Some(command) => {
145                    ControlFlow::Break(KeyEventResult::consumed(command).should_render(true))
146                }
147                None => ControlFlow::Continue(()),
148            }) {
149            ControlFlow::Break(r) => r,
150            ControlFlow::Continue(()) => KeyEventResult::Ignored,
151        }
152    }
153}
154
155impl Default for Keymaps {
156    fn default() -> Self {
157        Self::new(KeymapsConfig::default())
158    }
159}
160
161#[derive(Clone, Debug)]
162pub(crate) enum KeyTrie {
163    Command(Command),
164    Node(KeyTrieNode),
165}
166
167impl KeyTrie {
168    pub fn search(&self, keys: &[KeyEvent]) -> Option<KeyTrie> {
169        let mut trie = self;
170        for key in keys {
171            trie = match trie {
172                KeyTrie::Command(_) => return Some(trie.clone()),
173                KeyTrie::Node(trie) => trie.map.get(key)?,
174            }
175        }
176        Some(trie.clone())
177    }
178}
179
180impl Default for KeyTrie {
181    fn default() -> Self {
182        KeyTrie::Node(KeyTrieNode {
183            map: HashMap::new(),
184        })
185    }
186}
187
188#[derive(Clone, Debug)]
189pub struct KeyTrieNode {
190    map: HashMap<KeyEvent, KeyTrie>,
191}
192
193fn parse(s: &str) -> anyhow::Result<KeyEvent> {
194    let mut tokens: Vec<_> = s.split('-').collect();
195    let code = match tokens.pop().ok_or_else(|| anyhow!("no token"))? {
196        "enter" => KeyCode::Enter,
197        "tab" => KeyCode::Tab,
198        "backtab" => KeyCode::BackTab,
199        "left" => KeyCode::Left,
200        "right" => KeyCode::Right,
201        "up" => KeyCode::Up,
202        "down" => KeyCode::Down,
203        "esc" => KeyCode::Esc,
204        "space" => KeyCode::Char(' '),
205        single if single.chars().count() == 1 => KeyCode::Char(single.chars().next().unwrap()),
206        undefined => bail!("`{undefined}` is not implemented yet"),
207    };
208
209    let mut modifiers = KeyModifiers::NONE;
210    for token in tokens {
211        let modifier = match token {
212            "C" => KeyModifiers::CONTROL,
213            "S" => KeyModifiers::SHIFT,
214            "A" => KeyModifiers::ALT,
215            undefined => bail!("`{undefined}` modifier is not implemented yet"),
216        };
217        modifiers.insert(modifier);
218    }
219    // Handling special case
220    if code == KeyCode::BackTab {
221        modifiers.insert(KeyModifiers::SHIFT);
222    }
223    Ok(KeyEvent::new(code, modifiers))
224}