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::Char('g'), KeyModifiers::NONE, "revert");
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        if let KeyCode::Char(c) = code {
130            if c.is_ascii_uppercase() {
131                // Uppercase char -> always normalize to lowercase with SHIFT
132                return (
133                    KeyCode::Char(c.to_ascii_lowercase()),
134                    modifiers | KeyModifiers::SHIFT,
135                );
136            }
137            // Lowercase chars: keep as-is (SHIFT modifier preserved if present)
138        }
139        (code, modifiers)
140    }
141
142    /// Resolve a keybinding for a mode, following inheritance chain
143    ///
144    /// Returns the command name if a binding is found in this mode or any parent.
145    pub fn resolve_keybinding(
146        &self,
147        mode_name: &str,
148        code: KeyCode,
149        modifiers: KeyModifiers,
150    ) -> Option<String> {
151        let mut current_mode_name = Some(mode_name);
152
153        // Normalize the key for consistent lookup
154        let (code, modifiers) = Self::normalize_key(code, modifiers);
155
156        // Walk up the inheritance chain
157        while let Some(name) = current_mode_name {
158            if let Some(mode) = self.modes.get(name) {
159                // Check if this mode has the keybinding
160                if let Some(command) = mode.keybindings.get(&(code, modifiers)) {
161                    return Some(command.clone());
162                }
163
164                // Move to parent mode
165                current_mode_name = mode.parent.as_deref();
166            } else {
167                // Mode not found, stop searching
168                break;
169            }
170        }
171
172        None
173    }
174
175    /// Check if a mode is read-only (checking inheritance)
176    pub fn is_read_only(&self, mode_name: &str) -> bool {
177        let mut current_mode_name = Some(mode_name);
178
179        // Walk up the inheritance chain
180        while let Some(name) = current_mode_name {
181            if let Some(mode) = self.modes.get(name) {
182                if mode.read_only {
183                    return true;
184                }
185                current_mode_name = mode.parent.as_deref();
186            } else {
187                break;
188            }
189        }
190
191        false
192    }
193
194    /// Check if a key sequence could be the start of a chord in this mode
195    ///
196    /// This is used to determine if we should wait for more keys before
197    /// deciding what action to take.
198    pub fn is_chord_prefix(
199        &self,
200        mode_name: &str,
201        chord_state: &[(KeyCode, KeyModifiers)],
202        code: KeyCode,
203        modifiers: KeyModifiers,
204    ) -> bool {
205        // Normalize the key
206        let (code, modifiers) = Self::normalize_key(code, modifiers);
207
208        // Build the sequence we're checking
209        let mut sequence: Vec<(KeyCode, KeyModifiers)> = chord_state
210            .iter()
211            .map(|(c, m)| Self::normalize_key(*c, *m))
212            .collect();
213        sequence.push((code, modifiers));
214
215        let mut current_mode_name = Some(mode_name);
216
217        // Walk up the inheritance chain
218        while let Some(name) = current_mode_name {
219            if let Some(mode) = self.modes.get(name) {
220                // Check if our sequence is a prefix of any chord binding
221                for chord_seq in mode.chord_keybindings.keys() {
222                    if chord_seq.len() > sequence.len()
223                        && chord_seq[..sequence.len()] == sequence[..]
224                    {
225                        return true;
226                    }
227                }
228                current_mode_name = mode.parent.as_deref();
229            } else {
230                break;
231            }
232        }
233
234        false
235    }
236
237    /// Resolve a chord keybinding (multi-key sequence) for a mode
238    ///
239    /// Returns the command name if the full sequence matches a chord binding.
240    pub fn resolve_chord_keybinding(
241        &self,
242        mode_name: &str,
243        chord_state: &[(KeyCode, KeyModifiers)],
244        code: KeyCode,
245        modifiers: KeyModifiers,
246    ) -> Option<String> {
247        // Normalize the key
248        let (code, modifiers) = Self::normalize_key(code, modifiers);
249
250        // Build the full sequence
251        let mut sequence: Vec<(KeyCode, KeyModifiers)> = chord_state
252            .iter()
253            .map(|(c, m)| Self::normalize_key(*c, *m))
254            .collect();
255        sequence.push((code, modifiers));
256
257        tracing::trace!(
258            "resolve_chord_keybinding: mode={}, sequence={:?}",
259            mode_name,
260            sequence
261        );
262
263        let mut current_mode_name = Some(mode_name);
264
265        // Walk up the inheritance chain
266        while let Some(name) = current_mode_name {
267            if let Some(mode) = self.modes.get(name) {
268                // Check for exact match
269                if let Some(command) = mode.chord_keybindings.get(&sequence) {
270                    tracing::trace!("  -> found chord binding: {}", command);
271                    return Some(command.clone());
272                }
273                current_mode_name = mode.parent.as_deref();
274            } else {
275                break;
276            }
277        }
278
279        tracing::trace!("  -> no chord binding found");
280        None
281    }
282
283    /// List all registered mode names
284    pub fn list_modes(&self) -> Vec<String> {
285        self.modes.keys().cloned().collect()
286    }
287
288    /// Check if a mode exists
289    pub fn has_mode(&self, name: &str) -> bool {
290        self.modes.contains_key(name)
291    }
292
293    /// Get all keybindings for a mode (including inherited ones)
294    ///
295    /// Returns bindings from most specific (this mode) to least specific (root parent).
296    /// Later bindings override earlier ones.
297    pub fn get_all_keybindings(&self, mode_name: &str) -> HashMap<(KeyCode, KeyModifiers), String> {
298        let mut result = HashMap::new();
299        let mut chain = Vec::new();
300
301        // Build inheritance chain (root first)
302        let mut current = Some(mode_name);
303        while let Some(name) = current {
304            if let Some(mode) = self.modes.get(name) {
305                chain.push(mode);
306                current = mode.parent.as_deref();
307            } else {
308                break;
309            }
310        }
311
312        // Apply bindings from root to leaf (so leaf overrides)
313        for mode in chain.into_iter().rev() {
314            result.extend(mode.keybindings.clone());
315        }
316
317        result
318    }
319}
320
321impl Default for ModeRegistry {
322    fn default() -> Self {
323        Self::new()
324    }
325}
326
327#[cfg(test)]
328mod tests {
329    use super::*;
330
331    #[test]
332    fn test_special_mode_exists() {
333        let registry = ModeRegistry::new();
334        assert!(registry.has_mode("special"));
335    }
336
337    #[test]
338    fn test_special_mode_keybindings() {
339        let registry = ModeRegistry::new();
340        let mode = registry.get("special").unwrap();
341
342        assert_eq!(
343            mode.keybindings
344                .get(&(KeyCode::Char('q'), KeyModifiers::NONE)),
345            Some(&"close".to_string())
346        );
347        assert_eq!(
348            mode.keybindings
349                .get(&(KeyCode::Char('g'), KeyModifiers::NONE)),
350            Some(&"revert".to_string())
351        );
352    }
353
354    #[test]
355    fn test_mode_inheritance() {
356        let mut registry = ModeRegistry::new();
357
358        // Create a child mode that inherits from special
359        let diagnostics_mode = BufferMode::new("diagnostics-list")
360            .with_parent("special")
361            .with_binding(KeyCode::Enter, KeyModifiers::NONE, "diagnostics:goto")
362            .with_binding(KeyCode::Char('n'), KeyModifiers::NONE, "next-line");
363
364        registry.register(diagnostics_mode);
365
366        // Should find direct binding
367        assert_eq!(
368            registry.resolve_keybinding("diagnostics-list", KeyCode::Enter, KeyModifiers::NONE),
369            Some("diagnostics:goto".to_string())
370        );
371
372        // Should find inherited binding from special mode
373        assert_eq!(
374            registry.resolve_keybinding("diagnostics-list", KeyCode::Char('q'), KeyModifiers::NONE),
375            Some("close".to_string())
376        );
377
378        // Should not find non-existent binding
379        assert_eq!(
380            registry.resolve_keybinding("diagnostics-list", KeyCode::Char('x'), KeyModifiers::NONE),
381            None
382        );
383    }
384
385    #[test]
386    fn test_mode_read_only_inheritance() {
387        let mut registry = ModeRegistry::new();
388
389        // Special mode is read-only
390        assert!(registry.is_read_only("special"));
391
392        // Child mode inherits read-only
393        let child_mode = BufferMode::new("child").with_parent("special");
394        registry.register(child_mode);
395        assert!(registry.is_read_only("child"));
396
397        // Non-special mode is not read-only
398        let editable_mode = BufferMode::new("editable");
399        registry.register(editable_mode);
400        assert!(!registry.is_read_only("editable"));
401    }
402
403    #[test]
404    fn test_get_all_keybindings() {
405        let mut registry = ModeRegistry::new();
406
407        let child_mode = BufferMode::new("child")
408            .with_parent("special")
409            .with_binding(KeyCode::Enter, KeyModifiers::NONE, "child:action")
410            // Override parent binding
411            .with_binding(KeyCode::Char('q'), KeyModifiers::NONE, "child:quit");
412
413        registry.register(child_mode);
414
415        let all_bindings = registry.get_all_keybindings("child");
416
417        // Should have overridden 'q'
418        assert_eq!(
419            all_bindings.get(&(KeyCode::Char('q'), KeyModifiers::NONE)),
420            Some(&"child:quit".to_string())
421        );
422
423        // Should have inherited 'g'
424        assert_eq!(
425            all_bindings.get(&(KeyCode::Char('g'), KeyModifiers::NONE)),
426            Some(&"revert".to_string())
427        );
428
429        // Should have child-specific binding
430        assert_eq!(
431            all_bindings.get(&(KeyCode::Enter, KeyModifiers::NONE)),
432            Some(&"child:action".to_string())
433        );
434    }
435}