Skip to main content

scarab_plugin_api/key_tables/
mod.rs

1//! Key Tables & Modal Editing
2//!
3//! This module implements WezTerm-style key tables for modal keyboard configurations.
4//! Different key bindings can be active in different contexts (resize mode, copy mode, etc).
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::time::Duration;
9
10pub mod defaults;
11pub mod leader;
12pub mod stack;
13
14pub use defaults::{
15    default_copy_mode_table, default_resize_mode_table, default_search_mode_table, KeyTableRegistry,
16};
17pub use leader::{LeaderKeyConfig, LeaderKeyState};
18pub use stack::{KeyTableActivation, KeyTableStack};
19
20/// A named key table containing key bindings
21#[derive(Clone, Debug, Serialize, Deserialize)]
22pub struct KeyTable {
23    /// Name of the key table (e.g., "resize_pane", "copy_mode")
24    pub name: String,
25    /// Map of key combinations to actions
26    pub bindings: HashMap<KeyCombo, KeyAction>,
27}
28
29impl KeyTable {
30    /// Create a new empty key table
31    pub fn new(name: impl Into<String>) -> Self {
32        Self {
33            name: name.into(),
34            bindings: HashMap::new(),
35        }
36    }
37
38    /// Add a key binding to this table
39    pub fn bind(&mut self, combo: KeyCombo, action: KeyAction) {
40        self.bindings.insert(combo, action);
41    }
42
43    /// Look up an action for a key combination
44    pub fn get(&self, combo: &KeyCombo) -> Option<&KeyAction> {
45        self.bindings.get(combo)
46    }
47}
48
49/// A key combination: key code + modifiers
50#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
51pub struct KeyCombo {
52    /// The key code
53    pub key: KeyCode,
54    /// Modifier keys
55    pub mods: KeyModifiers,
56}
57
58impl KeyCombo {
59    /// Create a new key combination
60    pub fn new(key: KeyCode, mods: KeyModifiers) -> Self {
61        Self { key, mods }
62    }
63
64    /// Create a key combination with no modifiers
65    pub fn key(key: KeyCode) -> Self {
66        Self {
67            key,
68            mods: KeyModifiers::NONE,
69        }
70    }
71
72    /// Create a key combination with Ctrl modifier
73    pub fn ctrl(key: KeyCode) -> Self {
74        Self {
75            key,
76            mods: KeyModifiers::CTRL,
77        }
78    }
79
80    /// Create a key combination with Shift modifier
81    pub fn shift(key: KeyCode) -> Self {
82        Self {
83            key,
84            mods: KeyModifiers::SHIFT,
85        }
86    }
87
88    /// Create a key combination with Alt modifier
89    pub fn alt(key: KeyCode) -> Self {
90        Self {
91            key,
92            mods: KeyModifiers::ALT,
93        }
94    }
95
96    /// Create a key combination with Super modifier
97    pub fn super_key(key: KeyCode) -> Self {
98        Self {
99            key,
100            mods: KeyModifiers::SUPER,
101        }
102    }
103}
104
105/// Key codes - simplified representation
106/// In practice, this would integrate with the windowing system's key codes
107#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
108pub enum KeyCode {
109    // Letters
110    KeyA,
111    KeyB,
112    KeyC,
113    KeyD,
114    KeyE,
115    KeyF,
116    KeyG,
117    KeyH,
118    KeyI,
119    KeyJ,
120    KeyK,
121    KeyL,
122    KeyM,
123    KeyN,
124    KeyO,
125    KeyP,
126    KeyQ,
127    KeyR,
128    KeyS,
129    KeyT,
130    KeyU,
131    KeyV,
132    KeyW,
133    KeyX,
134    KeyY,
135    KeyZ,
136
137    // Numbers
138    Digit0,
139    Digit1,
140    Digit2,
141    Digit3,
142    Digit4,
143    Digit5,
144    Digit6,
145    Digit7,
146    Digit8,
147    Digit9,
148
149    // Function keys
150    F1,
151    F2,
152    F3,
153    F4,
154    F5,
155    F6,
156    F7,
157    F8,
158    F9,
159    F10,
160    F11,
161    F12,
162
163    // Special keys
164    Escape,
165    Enter,
166    Tab,
167    Backspace,
168    Space,
169    Slash,
170
171    // Arrow keys
172    Left,
173    Right,
174    Up,
175    Down,
176
177    // Navigation
178    Home,
179    End,
180    PageUp,
181    PageDown,
182    Insert,
183    Delete,
184
185    // Modifiers (when pressed alone)
186    ControlLeft,
187    ControlRight,
188    AltLeft,
189    AltRight,
190    ShiftLeft,
191    ShiftRight,
192    SuperLeft,
193    SuperRight,
194}
195
196bitflags::bitflags! {
197    /// Modifier key flags
198    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
199    pub struct KeyModifiers: u8 {
200        /// No modifiers
201        const NONE = 0;
202        /// Control key
203        const CTRL = 1 << 0;
204        /// Alt/Option key
205        const ALT = 1 << 1;
206        /// Shift key
207        const SHIFT = 1 << 2;
208        /// Super/Windows/Command key
209        const SUPER = 1 << 3;
210        /// Leader key (virtual modifier)
211        const LEADER = 1 << 4;
212    }
213}
214
215impl KeyModifiers {
216    /// Check if Ctrl is pressed
217    pub fn ctrl(self) -> bool {
218        self.contains(KeyModifiers::CTRL)
219    }
220
221    /// Check if Alt is pressed
222    pub fn alt(self) -> bool {
223        self.contains(KeyModifiers::ALT)
224    }
225
226    /// Check if Shift is pressed
227    pub fn shift(self) -> bool {
228        self.contains(KeyModifiers::SHIFT)
229    }
230
231    /// Check if Super is pressed
232    pub fn super_key(self) -> bool {
233        self.contains(KeyModifiers::SUPER)
234    }
235
236    /// Check if Leader is active
237    pub fn leader(self) -> bool {
238        self.contains(KeyModifiers::LEADER)
239    }
240}
241
242/// How to activate a key table
243#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
244pub enum ActivateKeyTableMode {
245    /// Table stays active until explicitly popped
246    Persistent,
247    /// Table pops after any keypress (one-shot mode)
248    OneShot,
249    /// Table has a timeout
250    Timeout(Duration),
251    /// Table stays until a specific action is triggered
252    UntilAction(Box<KeyAction>),
253}
254
255/// Actions that can be triggered by key bindings
256#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
257pub enum KeyAction {
258    // Table management
259    /// Activate a key table by name
260    ActivateKeyTable {
261        name: String,
262        mode: ActivateKeyTableMode,
263        replace_current: bool,
264    },
265    /// Pop the current key table from the stack
266    PopKeyTable,
267    /// Clear the entire key table stack
268    ClearKeyTableStack,
269
270    // Pane actions
271    /// Activate pane in a direction
272    ActivatePaneDirection(Direction),
273    /// Adjust pane size
274    AdjustPaneSize { direction: Direction, amount: i32 },
275    /// Split pane
276    SplitPane { direction: SplitDirection },
277    /// Close current pane
278    ClosePane,
279    /// Zoom/maximize current pane
280    ZoomPane,
281
282    // Tab actions
283    /// Activate tab by index (0-based)
284    ActivateTab(i32),
285    /// Activate tab relative to current (+1, -1, etc)
286    ActivateTabRelative(i32),
287    /// Create new tab
288    SpawnTab,
289    /// Close current tab
290    CloseTab,
291    /// Move tab to index
292    MoveTab(i32),
293
294    // Window actions
295    /// Create new window
296    SpawnWindow,
297    /// Close current window
298    CloseWindow,
299    /// Toggle fullscreen
300    ToggleFullscreen,
301
302    // Terminal actions
303    /// Send a string to the terminal
304    SendString(String),
305    /// Send a specific key combination
306    SendKey { key: KeyCode, mods: KeyModifiers },
307    /// Scroll by pages
308    ScrollByPage(i32),
309    /// Scroll by lines
310    ScrollByLine(i32),
311    /// Scroll to top
312    ScrollToTop,
313    /// Scroll to bottom
314    ScrollToBottom,
315    /// Clear scrollback buffer
316    ClearScrollback,
317
318    // Clipboard
319    /// Copy selection
320    Copy,
321    /// Paste from clipboard
322    Paste,
323    /// Copy to specific clipboard
324    CopyTo(ClipboardKind),
325    /// Paste from specific clipboard
326    PasteFrom(ClipboardKind),
327
328    // Mode actions
329    /// Enter copy mode
330    ActivateCopyMode,
331    /// Enter search mode
332    ActivateSearchMode,
333    /// Copy mode specific actions
334    CopyMode(CopyModeAction),
335    /// Search mode specific actions
336    Search(SearchAction),
337
338    // Custom actions
339    /// Emit a custom event
340    EmitEvent { event: String, args: Vec<String> },
341    /// Run a shell command
342    RunCommand(String),
343    /// No operation
344    Noop,
345}
346
347/// Directional navigation
348#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
349pub enum Direction {
350    Left,
351    Right,
352    Up,
353    Down,
354}
355
356/// Split direction
357#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
358pub enum SplitDirection {
359    Horizontal,
360    Vertical,
361}
362
363/// Clipboard kinds
364#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
365pub enum ClipboardKind {
366    /// Primary selection (X11)
367    Primary,
368    /// System clipboard
369    Clipboard,
370}
371
372/// Copy mode specific actions
373#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
374pub enum CopyModeAction {
375    // Movement
376    MoveLeft,
377    MoveRight,
378    MoveUp,
379    MoveDown,
380    MoveWordForward,
381    MoveWordBackward,
382    MoveToLineStart,
383    MoveToLineEnd,
384    MoveToTop,
385    MoveToBottom,
386
387    // Selection
388    ToggleSelection,
389    ToggleLineSelection,
390    ToggleBlockSelection,
391
392    // Search
393    SearchForward,
394    SearchBackward,
395    NextMatch,
396    PrevMatch,
397
398    // Actions
399    CopyAndExit,
400    Exit,
401}
402
403/// Search mode specific actions
404#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
405pub enum SearchAction {
406    /// Confirm search and find first match
407    Confirm,
408    /// Cancel search
409    Cancel,
410    /// Go to next match
411    NextMatch,
412    /// Go to previous match
413    PrevMatch,
414}
415
416#[cfg(test)]
417mod tests {
418    use super::*;
419
420    #[test]
421    fn test_key_modifiers_flags() {
422        let mods = KeyModifiers::CTRL | KeyModifiers::SHIFT;
423        assert!(mods.ctrl());
424        assert!(mods.shift());
425        assert!(!mods.alt());
426        assert!(!mods.super_key());
427        assert!(!mods.leader());
428    }
429
430    #[test]
431    fn test_key_modifiers_none() {
432        let mods = KeyModifiers::NONE;
433        assert!(!mods.ctrl());
434        assert!(!mods.alt());
435        assert!(!mods.shift());
436        assert!(!mods.super_key());
437        assert!(!mods.leader());
438    }
439
440    #[test]
441    fn test_key_combo_equality() {
442        let combo1 = KeyCombo::new(KeyCode::KeyH, KeyModifiers::CTRL);
443        let combo2 = KeyCombo::new(KeyCode::KeyH, KeyModifiers::CTRL);
444        let combo3 = KeyCombo::new(KeyCode::KeyH, KeyModifiers::ALT);
445
446        assert_eq!(combo1, combo2);
447        assert_ne!(combo1, combo3);
448    }
449
450    #[test]
451    fn test_key_table_binding() {
452        let mut table = KeyTable::new("test");
453        let combo = KeyCombo::new(KeyCode::KeyH, KeyModifiers::NONE);
454        let action = KeyAction::Noop;
455
456        table.bind(combo.clone(), action.clone());
457
458        assert_eq!(table.get(&combo), Some(&action));
459    }
460
461    #[test]
462    fn test_key_combo_hash() {
463        use std::collections::HashSet;
464
465        let mut set = HashSet::new();
466        let combo = KeyCombo::new(KeyCode::KeyA, KeyModifiers::CTRL);
467
468        set.insert(combo.clone());
469        assert!(set.contains(&combo));
470    }
471}