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 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 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 if code == KeyCode::BackTab {
221 modifiers.insert(KeyModifiers::SHIFT);
222 }
223 Ok(KeyEvent::new(code, modifiers))
224}