Skip to main content

reovim_driver_codec/
store.rs

1//! Content codec stores for module self-registration.
2//!
3//! Provides [`ContentCodecFactoryStore`] and [`ContentClassifierStore`],
4//! registries where codec modules register their factories and classifiers
5//! during `init()`. This follows the same pattern as [`SyntaxFactoryStore`].
6//!
7//! # Self-Registration Pattern
8//!
9//! ```ignore
10//! // In codec-utf8 module's init():
11//! impl Module for CodecUtf8Module {
12//!     fn init(&mut self, ctx: &ModuleContext) -> ProbeResult {
13//!         let factory_store = ctx.services.get_or_create::<ContentCodecFactoryStore>();
14//!         factory_store.add_factory(Arc::new(Utf8CodecFactory::new()));
15//!
16//!         let classifier_store = ctx.services.get_or_create::<ContentClassifierStore>();
17//!         classifier_store.add(Arc::new(Utf8Classifier::new()));
18//!
19//!         ProbeResult::Success
20//!     }
21//! }
22//! ```
23
24use std::sync::Arc;
25
26use {parking_lot::RwLock, reovim_kernel::api::v1::Service};
27
28use crate::{ContentClassifier, ContentCodec, ContentCodecFactory, ContentType};
29
30/// Store for content codec factories registered by modules during init.
31///
32/// Modules register their factories here, and the pipeline queries it
33/// to find a codec for a given content type.
34#[derive(Default)]
35pub struct ContentCodecFactoryStore {
36    /// Registered factories.
37    factories: RwLock<Vec<Arc<dyn ContentCodecFactory>>>,
38}
39
40impl ContentCodecFactoryStore {
41    /// Create a new empty store.
42    #[must_use]
43    pub fn new() -> Self {
44        Self::default()
45    }
46
47    /// Add a factory to the store.
48    ///
49    /// Called by codec modules during `init()`.
50    pub fn add_factory(&self, factory: Arc<dyn ContentCodecFactory>) {
51        self.factories.write().push(factory);
52    }
53
54    /// Find and create a codec for the given content type.
55    ///
56    /// Iterates registered factories, calling `create()` on each.
57    /// Returns the first `Some` result.
58    #[must_use]
59    pub fn find(&self, content_type: &ContentType) -> Option<Box<dyn ContentCodec>> {
60        self.factories
61            .read()
62            .iter()
63            .find_map(|f| f.create(content_type))
64    }
65
66    /// Take all registered factories.
67    ///
68    /// Called by bootstrap after all modules have initialized.
69    /// This drains the store; subsequent calls return empty vec.
70    pub fn take_factories(&self) -> Vec<Arc<dyn ContentCodecFactory>> {
71        std::mem::take(&mut *self.factories.write())
72    }
73
74    /// Get the number of registered factories.
75    #[must_use]
76    pub fn len(&self) -> usize {
77        self.factories.read().len()
78    }
79
80    /// Check if no factories are registered.
81    #[must_use]
82    pub fn is_empty(&self) -> bool {
83        self.factories.read().is_empty()
84    }
85}
86
87impl Service for ContentCodecFactoryStore {}
88
89impl std::fmt::Debug for ContentCodecFactoryStore {
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        f.debug_struct("ContentCodecFactoryStore")
92            .field("count", &self.len())
93            .finish()
94    }
95}
96
97/// Store for content classifiers registered by modules during init.
98///
99/// Classifiers are priority-sorted (highest first). The first classifier
100/// to return `Some` wins.
101#[derive(Default)]
102pub struct ContentClassifierStore {
103    /// Registered classifiers.
104    classifiers: RwLock<Vec<Arc<dyn ContentClassifier>>>,
105}
106
107impl ContentClassifierStore {
108    /// Create a new empty store.
109    #[must_use]
110    pub fn new() -> Self {
111        Self::default()
112    }
113
114    /// Add a classifier to the store.
115    ///
116    /// Called by codec modules during `init()`.
117    pub fn add(&self, classifier: Arc<dyn ContentClassifier>) {
118        self.classifiers.write().push(classifier);
119    }
120
121    /// Classify raw bytes to determine content type.
122    ///
123    /// Sorts classifiers by priority (highest first), then iterates
124    /// calling `classify()` on each. Returns the first `Some` result.
125    ///
126    /// Returns `None` if no classifier recognizes the content.
127    /// The caller should treat `None` as UTF-8 fallback.
128    #[must_use]
129    pub fn classify(&self, raw: &[u8], path: &str) -> Option<ContentType> {
130        let mut sorted: Vec<_> = self.classifiers.read().iter().cloned().collect();
131        sorted.sort_by_key(|c| std::cmp::Reverse(c.priority()));
132
133        for classifier in &sorted {
134            if let Some(content_type) = classifier.classify(raw, path) {
135                return Some(content_type);
136            }
137        }
138        None
139    }
140
141    /// Take all registered classifiers.
142    pub fn take_classifiers(&self) -> Vec<Arc<dyn ContentClassifier>> {
143        std::mem::take(&mut *self.classifiers.write())
144    }
145
146    /// Get the number of registered classifiers.
147    #[must_use]
148    pub fn len(&self) -> usize {
149        self.classifiers.read().len()
150    }
151
152    /// Check if no classifiers are registered.
153    #[must_use]
154    pub fn is_empty(&self) -> bool {
155        self.classifiers.read().is_empty()
156    }
157}
158
159impl Service for ContentClassifierStore {}
160
161impl std::fmt::Debug for ContentClassifierStore {
162    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163        f.debug_struct("ContentClassifierStore")
164            .field("count", &self.len())
165            .finish()
166    }
167}
168
169#[cfg(test)]
170#[path = "store_tests.rs"]
171mod tests;