Skip to main content

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;