reovim_driver_syntax/state.rs
1//! Per-session syntax driver storage.
2//!
3//! Stores per-buffer syntax drivers using the `ExtensionMap` pattern.
4//! This maintains "per-buffer" semantics while respecting kernel purity
5//! (kernel depends only on arch, not on syntax drivers).
6//!
7//! # Architecture
8//!
9//! ```text
10//! Session
11//! └─ ExtensionMap
12//! └─ SyntaxSessionState
13//! └─ HashMap<BufferId, Box<dyn SyntaxDriver>>
14//! ```
15//!
16//! # Usage from Modules
17//!
18//! Modules access syntax drivers via `ExtensionMap`:
19//! ```ignore
20//! let syntax = runtime.ext::<SyntaxSessionState>();
21//! if let Some(driver) = syntax.get(buffer_id) {
22//! let highlights = driver.highlights(0..1000);
23//! let folds = driver.folds();
24//! }
25//! ```
26
27use std::{collections::HashMap, sync::Arc};
28
29use {reovim_driver_session::SessionExtension, reovim_kernel::api::v1::BufferId};
30
31use crate::{LanguageRegistry, SyntaxDriver, SyntaxDriverFactory};
32
33/// Per-session syntax state stored in `ExtensionMap`.
34///
35/// Maps buffer IDs to their syntax drivers. Each buffer can have
36/// at most one syntax driver (language-specific highlighting).
37///
38/// # Design Rationale
39///
40/// Originally the plan called for storing syntax drivers in the kernel's
41/// `Buffer` struct. However, the kernel has a strict rule that it "depends
42/// only on arch". Storing drivers here in the session layer:
43///
44/// - Preserves kernel purity (no syntax dependency in kernel)
45/// - Maintains per-buffer semantics (each buffer has its own driver)
46/// - Uses existing `ExtensionMap` pattern (consistent with `VimSessionState`)
47///
48/// # Thread Safety
49///
50/// Access should be synchronized at the session level via `with_state_mut()`.
51#[derive(Default)]
52pub struct SyntaxSessionState {
53 /// Drivers per buffer (`BufferId.as_usize()` -> `SyntaxDriver`).
54 drivers: HashMap<usize, Box<dyn SyntaxDriver>>,
55 /// Optional factory for creating new drivers.
56 /// Uses `Arc` for shared ownership (populated from `SyntaxFactoryStore`).
57 factory: Option<Arc<dyn SyntaxDriverFactory>>,
58 /// Optional language registry for detecting language from file paths.
59 registry: Option<Arc<dyn LanguageRegistry>>,
60}
61
62impl SessionExtension for SyntaxSessionState {
63 fn create() -> Self {
64 Self::default()
65 }
66}
67
68impl SyntaxSessionState {
69 /// Create a new empty syntax state.
70 #[must_use]
71 pub fn new() -> Self {
72 Self::default()
73 }
74
75 /// Set the factory used to create new syntax drivers.
76 ///
77 /// Accepts `Arc` for shared ownership (populated from `SyntaxFactoryStore`).
78 pub fn set_factory(&mut self, factory: Arc<dyn SyntaxDriverFactory>) {
79 self.factory = Some(factory);
80 }
81
82 /// Get the factory (if set).
83 #[must_use]
84 pub fn factory(&self) -> Option<&dyn SyntaxDriverFactory> {
85 self.factory.as_deref()
86 }
87
88 /// Set the language registry used for language detection.
89 pub fn set_registry(&mut self, registry: Arc<dyn LanguageRegistry>) {
90 self.registry = Some(registry);
91 }
92
93 /// Get the language registry (if set).
94 #[must_use]
95 pub fn registry(&self) -> Option<&dyn LanguageRegistry> {
96 self.registry.as_deref()
97 }
98
99 /// Detect language from a file path using the registry.
100 ///
101 /// Returns `None` if no registry is set or the language is not recognized.
102 #[must_use]
103 pub fn detect_language(&self, path: &str) -> Option<String> {
104 self.registry.as_ref()?.detect_from_path(path)
105 }
106
107 /// Ensure a driver exists for a buffer by detecting language from file path.
108 ///
109 /// Combines language detection (via registry) and driver creation (via factory).
110 /// Returns `true` if a driver exists after the call.
111 pub fn ensure_driver_from_path(
112 &mut self,
113 buffer_id: BufferId,
114 path: &str,
115 content: &str,
116 ) -> bool {
117 // If driver already exists, done
118 if self.drivers.contains_key(&buffer_id.as_usize()) {
119 return true;
120 }
121
122 // Detect language from path
123 let Some(language_id) = self.detect_language(path) else {
124 return false;
125 };
126
127 // Delegate to ensure_driver
128 self.ensure_driver(buffer_id, &language_id, content)
129 }
130
131 /// Get a reference to the driver for a buffer.
132 #[must_use]
133 pub fn get(&self, buffer_id: BufferId) -> Option<&dyn SyntaxDriver> {
134 self.drivers
135 .get(&buffer_id.as_usize())
136 .map(|d| &**d as &dyn SyntaxDriver)
137 }
138
139 /// Get a mutable reference to the driver for a buffer.
140 pub fn get_mut(&mut self, buffer_id: BufferId) -> Option<&mut dyn SyntaxDriver> {
141 self.drivers
142 .get_mut(&buffer_id.as_usize())
143 .map(|d| &mut **d as &mut dyn SyntaxDriver)
144 }
145
146 /// Set the driver for a buffer.
147 ///
148 /// Replaces any existing driver for this buffer.
149 pub fn set(&mut self, buffer_id: BufferId, driver: Box<dyn SyntaxDriver>) {
150 self.drivers.insert(buffer_id.as_usize(), driver);
151 }
152
153 /// Remove the driver for a buffer.
154 ///
155 /// Call this when a buffer is closed to clean up resources.
156 pub fn remove(&mut self, buffer_id: BufferId) -> Option<Box<dyn SyntaxDriver>> {
157 self.drivers.remove(&buffer_id.as_usize())
158 }
159
160 /// Check if a buffer has a syntax driver.
161 #[must_use]
162 pub fn has_driver(&self, buffer_id: BufferId) -> bool {
163 self.drivers.contains_key(&buffer_id.as_usize())
164 }
165
166 /// Get or create a driver for a buffer.
167 ///
168 /// If no driver exists and a factory is set, attempts to create one
169 /// for the given language. Returns `false` if:
170 /// - No driver exists AND no factory is set
171 /// - No driver exists AND factory doesn't support the language
172 ///
173 /// After calling this, use `get_mut()` to access the driver.
174 pub fn ensure_driver(&mut self, buffer_id: BufferId, language_id: &str, content: &str) -> bool {
175 // If driver already exists, done
176 if self.drivers.contains_key(&buffer_id.as_usize()) {
177 return true;
178 }
179
180 // Try to create via factory
181 if let Some(factory) = &self.factory
182 && let Some(mut driver) = factory.create(language_id)
183 {
184 // Configure injection support: pass the factory so the driver
185 // can create child drivers for embedded languages.
186 driver.set_injection_factory(factory.clone());
187
188 // Parse initial content
189 driver.parse(content);
190 self.drivers.insert(buffer_id.as_usize(), driver);
191 return true;
192 }
193
194 false
195 }
196
197 /// Get the number of buffers with syntax drivers.
198 #[must_use]
199 pub fn len(&self) -> usize {
200 self.drivers.len()
201 }
202
203 /// Check if no buffers have syntax drivers.
204 #[must_use]
205 pub fn is_empty(&self) -> bool {
206 self.drivers.is_empty()
207 }
208
209 /// Clear all syntax drivers.
210 pub fn clear(&mut self) {
211 self.drivers.clear();
212 }
213}
214
215impl std::fmt::Debug for SyntaxSessionState {
216 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217 f.debug_struct("SyntaxSessionState")
218 .field("buffer_count", &self.drivers.len())
219 .field("has_factory", &self.factory.is_some())
220 .field("has_registry", &self.registry.is_some())
221 .finish()
222 }
223}
224
225#[cfg(test)]
226#[path = "state_tests.rs"]
227mod tests;