Skip to main content

fresh/input/
buffer_mode.rs

1//! Buffer mode system for buffer-local metadata
2//!
3//! Modes define per-buffer behavior: read-only state, text input handling,
4//! and plugin attribution. Keybinding resolution is handled by KeybindingResolver.
5
6use std::collections::HashMap;
7
8/// A buffer mode that defines behavior for a type of buffer
9#[derive(Debug, Clone)]
10pub struct BufferMode {
11    /// Name of this mode (e.g., "special", "diagnostics-list")
12    pub name: String,
13
14    /// Whether buffers with this mode are read-only by default
15    pub read_only: bool,
16
17    /// When true, unbound character keys in a read-only mode are dispatched as
18    /// `PluginAction("mode_text_input:<char>")` instead of being silently dropped.
19    /// This allows plugins to handle inline text editing (e.g. search fields)
20    /// without registering individual bindings for every character.
21    pub allow_text_input: bool,
22
23    /// Name of the plugin that registered this mode (for attribution in keybinding editor)
24    pub plugin_name: Option<String>,
25}
26
27impl BufferMode {
28    /// Create a new buffer mode
29    pub fn new(name: impl Into<String>) -> Self {
30        Self {
31            name: name.into(),
32            read_only: false,
33            allow_text_input: false,
34            plugin_name: None,
35        }
36    }
37
38    /// Set whether this mode is read-only by default
39    pub fn with_read_only(mut self, read_only: bool) -> Self {
40        self.read_only = read_only;
41        self
42    }
43
44    /// Set the plugin name for attribution
45    pub fn with_plugin_name(mut self, plugin_name: Option<String>) -> Self {
46        self.plugin_name = plugin_name;
47        self
48    }
49
50    /// Set whether unbound character keys should be dispatched as text input events
51    pub fn with_allow_text_input(mut self, allow: bool) -> Self {
52        self.allow_text_input = allow;
53        self
54    }
55}
56
57/// Registry for buffer modes — stores metadata only.
58///
59/// Keybinding resolution is handled by KeybindingResolver with Mode contexts.
60#[derive(Debug, Clone)]
61pub struct ModeRegistry {
62    /// All registered modes
63    modes: HashMap<String, BufferMode>,
64}
65
66impl ModeRegistry {
67    /// Create a new mode registry
68    pub fn new() -> Self {
69        Self {
70            modes: HashMap::new(),
71        }
72    }
73
74    /// Register a new mode
75    pub fn register(&mut self, mode: BufferMode) {
76        self.modes.insert(mode.name.clone(), mode);
77    }
78
79    /// Get a mode by name
80    pub fn get(&self, name: &str) -> Option<&BufferMode> {
81        self.modes.get(name)
82    }
83
84    /// Check if a mode is read-only
85    pub fn is_read_only(&self, mode_name: &str) -> bool {
86        self.modes
87            .get(mode_name)
88            .map(|m| m.read_only)
89            .unwrap_or(false)
90    }
91
92    /// Check if a mode allows text input passthrough
93    pub fn allows_text_input(&self, mode_name: &str) -> bool {
94        self.modes
95            .get(mode_name)
96            .map(|m| m.allow_text_input)
97            .unwrap_or(false)
98    }
99
100    /// List all registered mode names
101    pub fn list_modes(&self) -> Vec<String> {
102        self.modes.keys().cloned().collect()
103    }
104
105    /// Check if a mode exists
106    pub fn has_mode(&self, name: &str) -> bool {
107        self.modes.contains_key(name)
108    }
109}
110
111impl Default for ModeRegistry {
112    fn default() -> Self {
113        Self::new()
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn test_mode_metadata() {
123        let mut registry = ModeRegistry::new();
124
125        let mode = BufferMode::new("test-mode")
126            .with_read_only(true)
127            .with_allow_text_input(true)
128            .with_plugin_name(Some("test-plugin".to_string()));
129
130        registry.register(mode);
131
132        assert!(registry.has_mode("test-mode"));
133        assert!(registry.is_read_only("test-mode"));
134        assert!(registry.allows_text_input("test-mode"));
135        assert_eq!(
136            registry.get("test-mode").unwrap().plugin_name,
137            Some("test-plugin".to_string())
138        );
139    }
140
141    #[test]
142    fn test_mode_defaults() {
143        let registry = ModeRegistry::new();
144        assert!(!registry.is_read_only("nonexistent"));
145        assert!(!registry.allows_text_input("nonexistent"));
146    }
147}