Skip to main content

scarab_plugin_api/key_tables/
leader.rs

1//! Leader Key State
2//!
3//! Manages the leader key - a special modifier that becomes temporarily active
4//! after being pressed, similar to tmux's prefix key.
5
6use super::{KeyCode, KeyCombo, KeyModifiers};
7use serde::{Deserialize, Serialize};
8use std::time::{Duration, Instant};
9
10/// State of the leader key
11#[derive(Clone, Debug)]
12pub struct LeaderKeyState {
13    /// The key combination that activates the leader
14    pub key: KeyCombo,
15    /// Whether the leader is currently active
16    is_active: bool,
17    /// When the leader was activated (for timeout)
18    activated_at: Option<Instant>,
19    /// Timeout duration in milliseconds
20    timeout_ms: u64,
21    /// Optional prefix sequence (for multi-key leaders like "Ctrl+A, A")
22    prefix_sequence: Vec<KeyCombo>,
23    /// Keys pressed so far in the sequence
24    sequence_progress: Vec<KeyCombo>,
25}
26
27impl LeaderKeyState {
28    /// Create a new leader key state
29    pub fn new(key: KeyCombo, timeout_ms: u64) -> Self {
30        Self {
31            key,
32            is_active: false,
33            activated_at: None,
34            timeout_ms,
35            prefix_sequence: Vec::new(),
36            sequence_progress: Vec::new(),
37        }
38    }
39
40    /// Create a leader key state with a multi-key prefix sequence
41    pub fn with_sequence(sequence: Vec<KeyCombo>, timeout_ms: u64) -> Self {
42        let key = sequence
43            .first()
44            .cloned()
45            .unwrap_or_else(|| KeyCombo::new(KeyCode::KeyA, KeyModifiers::CTRL));
46
47        Self {
48            key,
49            is_active: false,
50            activated_at: None,
51            timeout_ms,
52            prefix_sequence: sequence,
53            sequence_progress: Vec::new(),
54        }
55    }
56
57    /// Check if the leader is currently active
58    pub fn is_active(&self) -> bool {
59        self.is_active
60    }
61
62    /// Get the timeout duration
63    pub fn timeout(&self) -> Duration {
64        Duration::from_millis(self.timeout_ms)
65    }
66
67    /// Activate the leader key
68    pub fn activate(&mut self) {
69        self.is_active = true;
70        self.activated_at = Some(Instant::now());
71    }
72
73    /// Deactivate the leader key
74    pub fn deactivate(&mut self) {
75        self.is_active = false;
76        self.activated_at = None;
77        self.sequence_progress.clear();
78    }
79
80    /// Reset the leader state (same as deactivate)
81    pub fn reset(&mut self) {
82        self.deactivate();
83    }
84
85    /// Feed a key to the leader state machine
86    ///
87    /// Returns true if the leader should be activated
88    pub fn feed_key(&mut self, combo: &KeyCombo) -> bool {
89        // If we have a prefix sequence, handle it
90        if !self.prefix_sequence.is_empty() {
91            return self.feed_sequence_key(combo);
92        }
93
94        // Simple single-key leader
95        if combo == &self.key {
96            self.activate();
97            true
98        } else {
99            false
100        }
101    }
102
103    /// Feed a key when using a prefix sequence
104    fn feed_sequence_key(&mut self, combo: &KeyCombo) -> bool {
105        // Add to progress
106        self.sequence_progress.push(combo.clone());
107
108        // Check if we match the sequence so far
109        if self.sequence_progress.len() <= self.prefix_sequence.len() {
110            let matches = self
111                .prefix_sequence
112                .iter()
113                .take(self.sequence_progress.len())
114                .zip(self.sequence_progress.iter())
115                .all(|(expected, actual)| expected == actual);
116
117            if !matches {
118                // Mismatch - reset
119                self.sequence_progress.clear();
120                return false;
121            }
122
123            // Check if we completed the sequence
124            if self.sequence_progress.len() == self.prefix_sequence.len() {
125                self.activate();
126                self.sequence_progress.clear();
127                return true;
128            }
129
130            // Still in progress
131            false
132        } else {
133            // Too many keys - reset
134            self.sequence_progress.clear();
135            false
136        }
137    }
138
139    /// Check if the leader has timed out
140    ///
141    /// Returns true if timeout occurred, false otherwise
142    pub fn check_timeout(&mut self) -> bool {
143        if let Some(activated) = self.activated_at {
144            if activated.elapsed().as_millis() as u64 > self.timeout_ms {
145                self.deactivate();
146                return true;
147            }
148        }
149        false
150    }
151
152    /// Get the time remaining before timeout (if active)
153    pub fn time_remaining(&self) -> Option<Duration> {
154        if let Some(activated) = self.activated_at {
155            let elapsed = activated.elapsed();
156            let timeout = Duration::from_millis(self.timeout_ms);
157            timeout.checked_sub(elapsed)
158        } else {
159            None
160        }
161    }
162
163    /// Get when the leader will timeout (if active)
164    pub fn timeout_at(&self) -> Option<Instant> {
165        self.activated_at
166            .map(|t| t + Duration::from_millis(self.timeout_ms))
167    }
168
169    /// Check if the sequence is in progress (for multi-key leaders)
170    pub fn is_sequence_in_progress(&self) -> bool {
171        !self.sequence_progress.is_empty()
172    }
173
174    /// Get the current sequence progress
175    pub fn sequence_progress(&self) -> &[KeyCombo] {
176        &self.sequence_progress
177    }
178
179    /// Update the leader key configuration
180    pub fn update_config(&mut self, key: KeyCombo, timeout_ms: u64) {
181        self.key = key;
182        self.timeout_ms = timeout_ms;
183        self.deactivate();
184    }
185}
186
187/// Configuration for leader key
188#[derive(Clone, Debug, Serialize, Deserialize)]
189pub struct LeaderKeyConfig {
190    /// The key combination
191    pub key: KeyCombo,
192    /// Timeout in milliseconds
193    pub timeout_ms: u64,
194    /// Optional multi-key sequence
195    pub sequence: Option<Vec<KeyCombo>>,
196}
197
198impl Default for LeaderKeyConfig {
199    fn default() -> Self {
200        Self {
201            key: KeyCombo::new(KeyCode::KeyA, KeyModifiers::CTRL),
202            timeout_ms: 1000,
203            sequence: None,
204        }
205    }
206}
207
208impl From<LeaderKeyConfig> for LeaderKeyState {
209    fn from(config: LeaderKeyConfig) -> Self {
210        if let Some(sequence) = config.sequence {
211            LeaderKeyState::with_sequence(sequence, config.timeout_ms)
212        } else {
213            LeaderKeyState::new(config.key, config.timeout_ms)
214        }
215    }
216}
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221
222    #[test]
223    fn test_leader_activation() {
224        let mut leader =
225            LeaderKeyState::new(KeyCombo::new(KeyCode::KeyA, KeyModifiers::CTRL), 1000);
226
227        assert!(!leader.is_active());
228
229        leader.activate();
230        assert!(leader.is_active());
231
232        leader.deactivate();
233        assert!(!leader.is_active());
234    }
235
236    #[test]
237    fn test_leader_timeout() {
238        let mut leader = LeaderKeyState::new(
239            KeyCombo::new(KeyCode::KeyA, KeyModifiers::CTRL),
240            100, // 100ms timeout
241        );
242
243        leader.activate();
244        assert!(leader.is_active());
245
246        // Should not timeout immediately
247        assert!(!leader.check_timeout());
248        assert!(leader.is_active());
249
250        // Simulate passage of time by sleeping
251        std::thread::sleep(Duration::from_millis(150));
252
253        // Should timeout now
254        assert!(leader.check_timeout());
255        assert!(!leader.is_active());
256    }
257
258    #[test]
259    fn test_leader_feed_key() {
260        let mut leader =
261            LeaderKeyState::new(KeyCombo::new(KeyCode::KeyA, KeyModifiers::CTRL), 1000);
262
263        let correct_key = KeyCombo::new(KeyCode::KeyA, KeyModifiers::CTRL);
264        let wrong_key = KeyCombo::new(KeyCode::KeyB, KeyModifiers::CTRL);
265
266        // Feeding the correct key should activate
267        assert!(leader.feed_key(&correct_key));
268        assert!(leader.is_active());
269
270        leader.deactivate();
271
272        // Feeding wrong key should not activate
273        assert!(!leader.feed_key(&wrong_key));
274        assert!(!leader.is_active());
275    }
276
277    #[test]
278    fn test_leader_sequence() {
279        let sequence = vec![
280            KeyCombo::new(KeyCode::KeyA, KeyModifiers::CTRL),
281            KeyCombo::new(KeyCode::KeyA, KeyModifiers::NONE),
282        ];
283
284        let mut leader = LeaderKeyState::with_sequence(sequence, 1000);
285
286        // First key in sequence
287        let key1 = KeyCombo::new(KeyCode::KeyA, KeyModifiers::CTRL);
288        assert!(!leader.feed_key(&key1)); // Not complete yet
289        assert!(!leader.is_active());
290
291        // Second key completes sequence
292        let key2 = KeyCombo::new(KeyCode::KeyA, KeyModifiers::NONE);
293        assert!(leader.feed_key(&key2));
294        assert!(leader.is_active());
295    }
296
297    #[test]
298    fn test_leader_sequence_mismatch() {
299        let sequence = vec![
300            KeyCombo::new(KeyCode::KeyA, KeyModifiers::CTRL),
301            KeyCombo::new(KeyCode::KeyA, KeyModifiers::NONE),
302        ];
303
304        let mut leader = LeaderKeyState::with_sequence(sequence, 1000);
305
306        // First key correct
307        let key1 = KeyCombo::new(KeyCode::KeyA, KeyModifiers::CTRL);
308        assert!(!leader.feed_key(&key1));
309
310        // Second key wrong
311        let key2 = KeyCombo::new(KeyCode::KeyB, KeyModifiers::NONE);
312        assert!(!leader.feed_key(&key2));
313        assert!(!leader.is_active());
314
315        // Should reset and not be in progress
316        assert!(!leader.is_sequence_in_progress());
317    }
318
319    #[test]
320    fn test_time_remaining() {
321        let mut leader =
322            LeaderKeyState::new(KeyCombo::new(KeyCode::KeyA, KeyModifiers::CTRL), 1000);
323
324        // Not active, no time remaining
325        assert!(leader.time_remaining().is_none());
326
327        leader.activate();
328
329        // Should have some time remaining
330        let remaining = leader.time_remaining();
331        assert!(remaining.is_some());
332        assert!(remaining.unwrap().as_millis() > 0);
333        assert!(remaining.unwrap().as_millis() <= 1000);
334    }
335
336    #[test]
337    fn test_reset() {
338        let mut leader =
339            LeaderKeyState::new(KeyCombo::new(KeyCode::KeyA, KeyModifiers::CTRL), 1000);
340
341        leader.activate();
342        assert!(leader.is_active());
343
344        leader.reset();
345        assert!(!leader.is_active());
346        assert!(leader.activated_at.is_none());
347    }
348
349    #[test]
350    fn test_update_config() {
351        let mut leader =
352            LeaderKeyState::new(KeyCombo::new(KeyCode::KeyA, KeyModifiers::CTRL), 1000);
353
354        leader.activate();
355        assert!(leader.is_active());
356
357        // Update config should deactivate
358        let new_key = KeyCombo::new(KeyCode::KeyB, KeyModifiers::CTRL);
359        leader.update_config(new_key.clone(), 2000);
360
361        assert!(!leader.is_active());
362        assert_eq!(leader.key, new_key);
363        assert_eq!(leader.timeout_ms, 2000);
364    }
365
366    #[test]
367    fn test_leader_config_default() {
368        let config = LeaderKeyConfig::default();
369        assert_eq!(config.timeout_ms, 1000);
370        assert_eq!(config.key.key, KeyCode::KeyA);
371        assert!(config.key.mods.ctrl());
372    }
373
374    #[test]
375    fn test_leader_from_config() {
376        let config = LeaderKeyConfig {
377            key: KeyCombo::new(KeyCode::KeyB, KeyModifiers::ALT),
378            timeout_ms: 500,
379            sequence: None,
380        };
381
382        let leader: LeaderKeyState = config.into();
383        assert_eq!(leader.key.key, KeyCode::KeyB);
384        assert!(leader.key.mods.alt());
385        assert_eq!(leader.timeout_ms, 500);
386    }
387}