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    /// When true, keys not bound by this mode fall through to the Normal-context
24    /// bindings (motion, selection, copy, …) instead of being dropped. Lets
25    /// viewer-style modes skip re-declaring every built-in cursor action.
26    pub inherit_normal_bindings: bool,
27
28    /// Name of the plugin that registered this mode (for attribution in keybinding editor)
29    pub plugin_name: Option<String>,
30}
31
32impl BufferMode {
33    /// Create a new buffer mode
34    pub fn new(name: impl Into<String>) -> Self {
35        Self {
36            name: name.into(),
37            read_only: false,
38            allow_text_input: false,
39            inherit_normal_bindings: false,
40            plugin_name: None,
41        }
42    }
43
44    /// Set whether this mode is read-only by default
45    pub fn with_read_only(mut self, read_only: bool) -> Self {
46        self.read_only = read_only;
47        self
48    }
49
50    /// Set the plugin name for attribution
51    pub fn with_plugin_name(mut self, plugin_name: Option<String>) -> Self {
52        self.plugin_name = plugin_name;
53        self
54    }
55
56    /// Set whether unbound character keys should be dispatched as text input events
57    pub fn with_allow_text_input(mut self, allow: bool) -> Self {
58        self.allow_text_input = allow;
59        self
60    }
61
62    /// Set whether unbound keys fall through to Normal-context bindings
63    pub fn with_inherit_normal_bindings(mut self, inherit: bool) -> Self {
64        self.inherit_normal_bindings = inherit;
65        self
66    }
67}
68
69/// Registry for buffer modes — stores metadata only.
70///
71/// Keybinding resolution is handled by KeybindingResolver with Mode contexts.
72#[derive(Debug, Clone)]
73pub struct ModeRegistry {
74    /// All registered modes
75    modes: HashMap<String, BufferMode>,
76}
77
78impl ModeRegistry {
79    /// Create a new mode registry
80    pub fn new() -> Self {
81        Self {
82            modes: HashMap::new(),
83        }
84    }
85
86    /// Register a new mode
87    pub fn register(&mut self, mode: BufferMode) {
88        self.modes.insert(mode.name.clone(), mode);
89    }
90
91    /// Get a mode by name
92    pub fn get(&self, name: &str) -> Option<&BufferMode> {
93        self.modes.get(name)
94    }
95
96    /// Check if a mode is read-only
97    pub fn is_read_only(&self, mode_name: &str) -> bool {
98        self.modes
99            .get(mode_name)
100            .map(|m| m.read_only)
101            .unwrap_or(false)
102    }
103
104    /// Check if a mode allows text input passthrough
105    pub fn allows_text_input(&self, mode_name: &str) -> bool {
106        self.modes
107            .get(mode_name)
108            .map(|m| m.allow_text_input)
109            .unwrap_or(false)
110    }
111
112    /// Check if a mode inherits Normal-context bindings for unbound keys
113    pub fn inherits_normal_bindings(&self, mode_name: &str) -> bool {
114        self.modes
115            .get(mode_name)
116            .map(|m| m.inherit_normal_bindings)
117            .unwrap_or(false)
118    }
119
120    /// List all registered mode names
121    pub fn list_modes(&self) -> Vec<String> {
122        self.modes.keys().cloned().collect()
123    }
124
125    /// Check if a mode exists
126    pub fn has_mode(&self, name: &str) -> bool {
127        self.modes.contains_key(name)
128    }
129}
130
131impl Default for ModeRegistry {
132    fn default() -> Self {
133        Self::new()
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[test]
142    fn test_mode_metadata() {
143        let mut registry = ModeRegistry::new();
144
145        let mode = BufferMode::new("test-mode")
146            .with_read_only(true)
147            .with_allow_text_input(true)
148            .with_plugin_name(Some("test-plugin".to_string()));
149
150        registry.register(mode);
151
152        assert!(registry.has_mode("test-mode"));
153        assert!(registry.is_read_only("test-mode"));
154        assert!(registry.allows_text_input("test-mode"));
155        assert_eq!(
156            registry.get("test-mode").unwrap().plugin_name,
157            Some("test-plugin".to_string())
158        );
159    }
160
161    #[test]
162    fn test_mode_defaults() {
163        let registry = ModeRegistry::new();
164        assert!(!registry.is_read_only("nonexistent"));
165        assert!(!registry.allows_text_input("nonexistent"));
166    }
167}