Skip to main content

fresh/input/
buffer_mode.rs

1//! Buffer mode system for buffer-local keybindings
2//!
3//! This module implements an Emacs-style major mode system where each buffer
4//! can have its own mode that defines keybindings. Modes support inheritance,
5//! allowing derived modes to extend parent modes.
6
7use crossterm::event::{KeyCode, KeyModifiers};
8use std::collections::HashMap;
9
10/// A buffer mode that defines keybindings and behavior for a type of buffer
11#[derive(Debug, Clone)]
12pub struct BufferMode {
13    /// Name of this mode (e.g., "special", "diagnostics-list")
14    pub name: String,
15
16    /// Parent mode name for inheritance (e.g., "special" is parent of "diagnostics-list")
17    pub parent: Option<String>,
18
19    /// Keybindings specific to this mode (key → command name)
20    pub keybindings: HashMap<(KeyCode, KeyModifiers), String>,
21
22    /// Chord keybindings (multi-key sequences like "g g" → command name)
23    pub chord_keybindings: HashMap<Vec<(KeyCode, KeyModifiers)>, String>,
24
25    /// Whether buffers with this mode are read-only by default
26    pub read_only: bool,
27}
28
29impl BufferMode {
30    /// Create a new buffer mode
31    pub fn new(name: impl Into<String>) -> Self {
32        Self {
33            name: name.into(),
34            parent: None,
35            keybindings: HashMap::new(),
36            chord_keybindings: HashMap::new(),
37            read_only: false,
38        }
39    }
40
41    /// Set the parent mode for inheritance
42    pub fn with_parent(mut self, parent: impl Into<String>) -> Self {
43        self.parent = Some(parent.into());
44        self
45    }
46
47    /// Add a keybinding to this mode
48    pub fn with_binding(
49        mut self,
50        code: KeyCode,
51        modifiers: KeyModifiers,
52        command: impl Into<String>,
53    ) -> Self {
54        self.keybindings.insert((code, modifiers), command.into());
55        self
56    }
57
58    /// Add a chord keybinding (multi-key sequence) to this mode
59    pub fn with_chord_binding(
60        mut self,
61        sequence: Vec<(KeyCode, KeyModifiers)>,
62        command: impl Into<String>,
63    ) -> Self {
64        self.chord_keybindings.insert(sequence, command.into());
65        self
66    }
67
68    /// Set whether this mode is read-only by default
69    pub fn with_read_only(mut self, read_only: bool) -> Self {
70        self.read_only = read_only;
71        self
72    }
73
74    /// Add multiple keybindings at once
75    pub fn with_bindings(mut self, bindings: Vec<(KeyCode, KeyModifiers, String)>) -> Self {
76        for (code, modifiers, command) in bindings {
77            self.keybindings.insert((code, modifiers), command);
78        }
79        self
80    }
81}
82
83/// Registry for buffer modes
84///
85/// Manages all available modes and provides lookup functionality with inheritance.
86#[derive(Debug, Clone)]
87pub struct ModeRegistry {
88    /// All registered modes
89    modes: HashMap<String, BufferMode>,
90}
91
92impl ModeRegistry {
93    /// Create a new mode registry with built-in modes
94    pub fn new() -> Self {
95        let mut registry = Self {
96            modes: HashMap::new(),
97        };
98
99        // Register built-in "special" mode (base for all special buffers)
100        // This is like Emacs' special-mode
101        // Keybindings map to Action names (see Action::from_str)
102        let special_mode = BufferMode::new("special")
103            .with_read_only(true)
104            .with_binding(KeyCode::Char('q'), KeyModifiers::NONE, "close")
105            .with_binding(KeyCode::Esc, KeyModifiers::NONE, "close");
106
107        registry.register(special_mode);
108
109        registry
110    }
111
112    /// Register a new mode
113    pub fn register(&mut self, mode: BufferMode) {
114        self.modes.insert(mode.name.clone(), mode);
115    }
116
117    /// Get a mode by name
118    pub fn get(&self, name: &str) -> Option<&BufferMode> {
119        self.modes.get(name)
120    }
121
122    /// Normalize a key for lookup: ensures consistent representation of shifted letters
123    /// This ensures that pressing 'G' (Shift+g) matches bindings defined as 'G'
124    ///
125    /// Normalization rules:
126    /// - Uppercase char (with or without SHIFT) -> lowercase char with SHIFT
127    /// - Lowercase char with SHIFT -> keep as is (already normalized form)
128    fn normalize_key(code: KeyCode, modifiers: KeyModifiers) -> (KeyCode, KeyModifiers) {
129        // BackTab already encodes Shift+Tab, so strip the redundant SHIFT modifier.
130        // This ensures "BackTab" in a mode definition matches the terminal's
131        // (BackTab, SHIFT) key event.
132        if code == KeyCode::BackTab {
133            return (code, modifiers.difference(KeyModifiers::SHIFT));
134        }
135        if let KeyCode::Char(c) = code {
136            if c.is_ascii_uppercase() {
137                // Uppercase char -> always normalize to lowercase with SHIFT
138                return (
139                    KeyCode::Char(c.to_ascii_lowercase()),
140                    modifiers | KeyModifiers::SHIFT,
141                );
142            }
143            // Lowercase chars: keep as-is (SHIFT modifier preserved if present)
144        }
145        (code, modifiers)
146    }
147
148    /// Resolve a keybinding for a mode, following inheritance chain
149    ///
150    /// Returns the command name if a binding is found in this mode or any parent.
151    pub fn resolve_keybinding(
152        &self,
153        mode_name: &str,
154        code: KeyCode,
155        modifiers: KeyModifiers,
156    ) -> Option<String> {
157        let mut current_mode_name = Some(mode_name);
158
159        // Normalize the key for consistent lookup
160        let (code, modifiers) = Self::normalize_key(code, modifiers);
161
162        // Walk up the inheritance chain
163        while let Some(name) = current_mode_name {
164            if let Some(mode) = self.modes.get(name) {
165                // Check if this mode has the keybinding
166                if let Some(command) = mode.keybindings.get(&(code, modifiers)) {
167                    return Some(command.clone());
168                }
169
170                // Move to parent mode
171                current_mode_name = mode.parent.as_deref();
172            } else {
173                // Mode not found, stop searching
174                break;
175            }
176        }
177
178        None
179    }
180
181    /// Check if a mode is read-only (checking inheritance)
182    pub fn is_read_only(&self, mode_name: &str) -> bool {
183        let mut current_mode_name = Some(mode_name);
184
185        // Walk up the inheritance chain
186        while let Some(name) = current_mode_name {
187            if let Some(mode) = self.modes.get(name) {
188                if mode.read_only {
189                    return true;
190                }
191                current_mode_name = mode.parent.as_deref();
192            } else {
193                break;
194            }
195        }
196
197        false
198    }
199
200    /// Check if a key sequence could be the start of a chord in this mode
201    ///
202    /// This is used to determine if we should wait for more keys before
203    /// deciding what action to take.
204    pub fn is_chord_prefix(
205        &self,
206        mode_name: &str,
207        chord_state: &[(KeyCode, KeyModifiers)],
208        code: KeyCode,
209        modifiers: KeyModifiers,
210    ) -> bool {
211        // Normalize the key
212        let (code, modifiers) = Self::normalize_key(code, modifiers);
213
214        // Build the sequence we're checking
215        let mut sequence: Vec<(KeyCode, KeyModifiers)> = chord_state
216            .iter()
217            .map(|(c, m)| Self::normalize_key(*c, *m))
218            .collect();
219        sequence.push((code, modifiers));
220
221        let mut current_mode_name = Some(mode_name);
222
223        // Walk up the inheritance chain
224        while let Some(name) = current_mode_name {
225            if let Some(mode) = self.modes.get(name) {
226                // Check if our sequence is a prefix of any chord binding
227                for chord_seq in mode.chord_keybindings.keys() {
228                    if chord_seq.len() > sequence.len()
229                        && chord_seq[..sequence.len()] == sequence[..]
230                    {
231                        return true;
232                    }
233                }
234                current_mode_name = mode.parent.as_deref();
235            } else {
236                break;
237            }
238        }
239
240        false
241    }
242
243    /// Resolve a chord keybinding (multi-key sequence) for a mode
244    ///
245    /// Returns the command name if the full sequence matches a chord binding.
246    pub fn resolve_chord_keybinding(
247        &self,
248        mode_name: &str,
249        chord_state: &[(KeyCode, KeyModifiers)],
250        code: KeyCode,
251        modifiers: KeyModifiers,
252    ) -> Option<String> {
253        // Normalize the key
254        let (code, modifiers) = Self::normalize_key(code, modifiers);
255
256        // Build the full sequence
257        let mut sequence: Vec<(KeyCode, KeyModifiers)> = chord_state
258            .iter()
259            .map(|(c, m)| Self::normalize_key(*c, *m))
260            .collect();
261        sequence.push((code, modifiers));
262
263        tracing::trace!(
264            "resolve_chord_keybinding: mode={}, sequence={:?}",
265            mode_name,
266            sequence
267        );
268
269        let mut current_mode_name = Some(mode_name);
270
271        // Walk up the inheritance chain
272        while let Some(name) = current_mode_name {
273            if let Some(mode) = self.modes.get(name) {
274                // Check for exact match
275                if let Some(command) = mode.chord_keybindings.get(&sequence) {
276                    tracing::trace!("  -> found chord binding: {}", command);
277                    return Some(command.clone());
278                }
279                current_mode_name = mode.parent.as_deref();
280            } else {
281                break;
282            }
283        }
284
285        tracing::trace!("  -> no chord binding found");
286        None
287    }
288
289    /// List all registered mode names
290    pub fn list_modes(&self) -> Vec<String> {
291        self.modes.keys().cloned().collect()
292    }
293
294    /// Check if a mode exists
295    pub fn has_mode(&self, name: &str) -> bool {
296        self.modes.contains_key(name)
297    }
298
299    /// Get all keybindings for a mode (including inherited ones)
300    ///
301    /// Returns bindings from most specific (this mode) to least specific (root parent).
302    /// Later bindings override earlier ones.
303    pub fn get_all_keybindings(&self, mode_name: &str) -> HashMap<(KeyCode, KeyModifiers), String> {
304        let mut result = HashMap::new();
305        let mut chain = Vec::new();
306
307        // Build inheritance chain (root first)
308        let mut current = Some(mode_name);
309        while let Some(name) = current {
310            if let Some(mode) = self.modes.get(name) {
311                chain.push(mode);
312                current = mode.parent.as_deref();
313            } else {
314                break;
315            }
316        }
317
318        // Apply bindings from root to leaf (so leaf overrides)
319        for mode in chain.into_iter().rev() {
320            result.extend(mode.keybindings.clone());
321        }
322
323        result
324    }
325}
326
327impl Default for ModeRegistry {
328    fn default() -> Self {
329        Self::new()
330    }
331}
332
333#[cfg(test)]
334mod tests {
335    use super::*;
336
337    #[test]
338    fn test_special_mode_exists() {
339        let registry = ModeRegistry::new();
340        assert!(registry.has_mode("special"));
341    }
342
343    #[test]
344    fn test_special_mode_keybindings() {
345        let registry = ModeRegistry::new();
346        let mode = registry.get("special").unwrap();
347
348        assert_eq!(
349            mode.keybindings
350                .get(&(KeyCode::Char('q'), KeyModifiers::NONE)),
351            Some(&"close".to_string())
352        );
353        assert_eq!(
354            mode.keybindings.get(&(KeyCode::Esc, KeyModifiers::NONE)),
355            Some(&"close".to_string())
356        );
357    }
358
359    #[test]
360    fn test_mode_inheritance() {
361        let mut registry = ModeRegistry::new();
362
363        // Create a child mode that inherits from special
364        let diagnostics_mode = BufferMode::new("diagnostics-list")
365            .with_parent("special")
366            .with_binding(KeyCode::Enter, KeyModifiers::NONE, "diagnostics:goto")
367            .with_binding(KeyCode::Char('n'), KeyModifiers::NONE, "next-line");
368
369        registry.register(diagnostics_mode);
370
371        // Should find direct binding
372        assert_eq!(
373            registry.resolve_keybinding("diagnostics-list", KeyCode::Enter, KeyModifiers::NONE),
374            Some("diagnostics:goto".to_string())
375        );
376
377        // Should find inherited binding from special mode
378        assert_eq!(
379            registry.resolve_keybinding("diagnostics-list", KeyCode::Char('q'), KeyModifiers::NONE),
380            Some("close".to_string())
381        );
382
383        // Should not find non-existent binding
384        assert_eq!(
385            registry.resolve_keybinding("diagnostics-list", KeyCode::Char('x'), KeyModifiers::NONE),
386            None
387        );
388    }
389
390    #[test]
391    fn test_mode_read_only_inheritance() {
392        let mut registry = ModeRegistry::new();
393
394        // Special mode is read-only
395        assert!(registry.is_read_only("special"));
396
397        // Child mode inherits read-only
398        let child_mode = BufferMode::new("child").with_parent("special");
399        registry.register(child_mode);
400        assert!(registry.is_read_only("child"));
401
402        // Non-special mode is not read-only
403        let editable_mode = BufferMode::new("editable");
404        registry.register(editable_mode);
405        assert!(!registry.is_read_only("editable"));
406    }
407
408    #[test]
409    fn test_get_all_keybindings() {
410        let mut registry = ModeRegistry::new();
411
412        let child_mode = BufferMode::new("child")
413            .with_parent("special")
414            .with_binding(KeyCode::Enter, KeyModifiers::NONE, "child:action")
415            // Override parent binding
416            .with_binding(KeyCode::Char('q'), KeyModifiers::NONE, "child:quit");
417
418        registry.register(child_mode);
419
420        let all_bindings = registry.get_all_keybindings("child");
421
422        // Should have overridden 'q'
423        assert_eq!(
424            all_bindings.get(&(KeyCode::Char('q'), KeyModifiers::NONE)),
425            Some(&"child:quit".to_string())
426        );
427
428        // Should have inherited Esc
429        assert_eq!(
430            all_bindings.get(&(KeyCode::Esc, KeyModifiers::NONE)),
431            Some(&"close".to_string())
432        );
433
434        // Should have child-specific binding
435        assert_eq!(
436            all_bindings.get(&(KeyCode::Enter, KeyModifiers::NONE)),
437            Some(&"child:action".to_string())
438        );
439    }
440}