scarab_plugin_api/key_tables/
stack.rs1use super::{ActivateKeyTableMode, KeyAction, KeyCombo, KeyTable};
6use serde::{Deserialize, Serialize};
7use std::time::{Duration, Instant};
8
9#[derive(Clone, Debug)]
11pub struct KeyTableStack {
12 stack: Vec<KeyTableActivation>,
14 default_table: KeyTable,
16}
17
18impl KeyTableStack {
19 pub fn new(default_table: KeyTable) -> Self {
21 Self {
22 stack: Vec::new(),
23 default_table,
24 }
25 }
26
27 pub fn push(&mut self, activation: KeyTableActivation) {
29 self.stack.push(activation);
30 }
31
32 pub fn pop(&mut self) -> Option<KeyTableActivation> {
34 self.stack.pop()
35 }
36
37 pub fn current(&self) -> Option<&KeyTableActivation> {
39 self.stack.last()
40 }
41
42 pub fn current_name(&self) -> &str {
44 self.current().map(|a| a.name.as_str()).unwrap_or("default")
45 }
46
47 pub fn clear(&mut self) {
49 self.stack.clear();
50 }
51
52 pub fn is_empty(&self) -> bool {
54 self.stack.is_empty()
55 }
56
57 pub fn len(&self) -> usize {
59 self.stack.len()
60 }
61
62 pub fn resolve(&self, combo: &KeyCombo) -> Option<&KeyAction> {
66 for activation in self.stack.iter().rev() {
68 if let Some(action) = activation.table.get(combo) {
69 return Some(action);
70 }
71 }
72
73 self.default_table.get(combo)
75 }
76
77 pub fn handle_key(&mut self, combo: KeyCombo, now: Instant) -> Option<KeyAction> {
81 self.expire_timeouts(now);
83
84 let action = self.resolve(&combo).cloned();
86
87 if let Some(top) = self.stack.last() {
89 if matches!(top.mode, ActivateKeyTableMode::OneShot) {
90 self.stack.pop();
91 }
92 }
93
94 action
95 }
96
97 fn expire_timeouts(&mut self, now: Instant) {
99 self.stack.retain(|activation| {
100 activation
101 .timeout
102 .map(|timeout| now < timeout)
103 .unwrap_or(true)
104 });
105 }
106
107 pub fn next_timeout(&self) -> Option<Instant> {
109 self.stack.iter().filter_map(|a| a.timeout).min()
110 }
111
112 pub fn default_table(&self) -> &KeyTable {
114 &self.default_table
115 }
116
117 pub fn default_table_mut(&mut self) -> &mut KeyTable {
119 &mut self.default_table
120 }
121}
122
123impl Default for KeyTableStack {
124 fn default() -> Self {
125 Self::new(KeyTable::new("default"))
126 }
127}
128
129#[derive(Clone, Debug, Serialize, Deserialize)]
131pub struct KeyTableActivation {
132 pub name: String,
134 pub table: KeyTable,
136 pub mode: ActivateKeyTableMode,
138 #[serde(skip)]
140 pub timeout: Option<Instant>,
141 pub replace_current: bool,
143}
144
145impl KeyTableActivation {
146 pub fn persistent(name: String, table: KeyTable) -> Self {
148 Self {
149 name,
150 table,
151 mode: ActivateKeyTableMode::Persistent,
152 timeout: None,
153 replace_current: false,
154 }
155 }
156
157 pub fn one_shot(name: String, table: KeyTable) -> Self {
159 Self {
160 name,
161 table,
162 mode: ActivateKeyTableMode::OneShot,
163 timeout: None,
164 replace_current: false,
165 }
166 }
167
168 pub fn timed(name: String, table: KeyTable, duration: Duration, now: Instant) -> Self {
170 Self {
171 name,
172 table,
173 mode: ActivateKeyTableMode::Timeout(duration),
174 timeout: Some(now + duration),
175 replace_current: false,
176 }
177 }
178
179 pub fn with_replace(mut self, replace: bool) -> Self {
181 self.replace_current = replace;
182 self
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189 use crate::key_tables::{KeyCode, KeyModifiers};
190
191 fn create_test_table(name: &str) -> KeyTable {
192 let mut table = KeyTable::new(name);
193 table.bind(
194 KeyCombo::new(KeyCode::KeyH, KeyModifiers::NONE),
195 KeyAction::Noop,
196 );
197 table
198 }
199
200 #[test]
201 fn test_stack_push_pop() {
202 let mut stack = KeyTableStack::default();
203 assert_eq!(stack.len(), 0);
204 assert!(stack.is_empty());
205
206 let activation = KeyTableActivation::persistent("test".into(), create_test_table("test"));
207 stack.push(activation);
208
209 assert_eq!(stack.len(), 1);
210 assert!(!stack.is_empty());
211 assert_eq!(stack.current_name(), "test");
212
213 stack.pop();
214 assert_eq!(stack.len(), 0);
215 assert!(stack.is_empty());
216 }
217
218 #[test]
219 fn test_key_resolution_stack() {
220 let mut stack = KeyTableStack::default();
221
222 let table1 = create_test_table("test1");
224 stack.push(KeyTableActivation::persistent("test1".into(), table1));
225
226 let combo = KeyCombo::new(KeyCode::KeyH, KeyModifiers::NONE);
228 assert!(stack.resolve(&combo).is_some());
229
230 let combo = KeyCombo::new(KeyCode::KeyJ, KeyModifiers::NONE);
232 assert!(stack.resolve(&combo).is_none());
233 }
234
235 #[test]
236 fn test_one_shot_pop() {
237 let mut stack = KeyTableStack::default();
238 let now = Instant::now();
239
240 let table = create_test_table("oneshot");
242 stack.push(KeyTableActivation::one_shot("oneshot".into(), table));
243
244 assert_eq!(stack.len(), 1);
245
246 let combo = KeyCombo::new(KeyCode::KeyX, KeyModifiers::NONE);
248 stack.handle_key(combo, now);
249
250 assert_eq!(stack.len(), 0);
251 }
252
253 #[test]
254 fn test_timeout_expiration() {
255 let mut stack = KeyTableStack::default();
256 let now = Instant::now();
257
258 let table = create_test_table("timed");
260 stack.push(KeyTableActivation::timed(
261 "timed".into(),
262 table,
263 Duration::from_millis(100),
264 now,
265 ));
266
267 assert_eq!(stack.len(), 1);
268
269 stack.expire_timeouts(now);
271 assert_eq!(stack.len(), 1);
272
273 let future = now + Duration::from_millis(101);
275 stack.expire_timeouts(future);
276 assert_eq!(stack.len(), 0);
277 }
278
279 #[test]
280 fn test_multiple_tables_resolution() {
281 let mut stack = KeyTableStack::default();
282
283 let mut table1 = KeyTable::new("bottom");
285 table1.bind(
286 KeyCombo::new(KeyCode::KeyH, KeyModifiers::NONE),
287 KeyAction::Noop,
288 );
289 stack.push(KeyTableActivation::persistent("bottom".into(), table1));
290
291 let mut table2 = KeyTable::new("top");
293 table2.bind(
294 KeyCombo::new(KeyCode::KeyH, KeyModifiers::NONE),
295 KeyAction::PopKeyTable,
296 );
297 stack.push(KeyTableActivation::persistent("top".into(), table2));
298
299 let combo = KeyCombo::new(KeyCode::KeyH, KeyModifiers::NONE);
301 let action = stack.resolve(&combo);
302 assert_eq!(action, Some(&KeyAction::PopKeyTable));
303 }
304
305 #[test]
306 fn test_clear_stack() {
307 let mut stack = KeyTableStack::default();
308
309 stack.push(KeyTableActivation::persistent(
310 "test1".into(),
311 create_test_table("test1"),
312 ));
313 stack.push(KeyTableActivation::persistent(
314 "test2".into(),
315 create_test_table("test2"),
316 ));
317
318 assert_eq!(stack.len(), 2);
319
320 stack.clear();
321 assert_eq!(stack.len(), 0);
322 assert!(stack.is_empty());
323 }
324
325 #[test]
326 fn test_next_timeout() {
327 let mut stack = KeyTableStack::default();
328 let now = Instant::now();
329
330 assert!(stack.next_timeout().is_none());
332
333 let table = create_test_table("timed");
335 stack.push(KeyTableActivation::timed(
336 "timed".into(),
337 table,
338 Duration::from_millis(100),
339 now,
340 ));
341
342 let next = stack.next_timeout();
343 assert!(next.is_some());
344 assert!(next.unwrap() > now);
345 }
346
347 #[test]
348 fn test_default_table_access() {
349 let mut stack = KeyTableStack::default();
350
351 stack.default_table_mut().bind(
353 KeyCombo::new(KeyCode::KeyA, KeyModifiers::CTRL),
354 KeyAction::Noop,
355 );
356
357 let combo = KeyCombo::new(KeyCode::KeyA, KeyModifiers::CTRL);
359 assert!(stack.resolve(&combo).is_some());
360 }
361}