reovim_plugin_treesitter/
registry.rs

1//! Language registry for dynamic language registration
2//!
3//! Language plugins implement the `LanguageSupport` trait and register
4//! themselves with the treesitter plugin at runtime.
5
6use std::{
7    collections::HashMap,
8    path::Path,
9    sync::{Arc, RwLock},
10};
11
12use tree_sitter::Language;
13
14/// Trait for language plugins to implement
15///
16/// Language plugins provide the tree-sitter grammar and query files
17/// for a specific language. They register themselves with the treesitter
18/// plugin using the `RegisterLanguage` event.
19pub trait LanguageSupport: Send + Sync + 'static {
20    /// Unique identifier for this language (e.g., "rust", "python")
21    fn language_id(&self) -> &'static str;
22
23    /// File extensions that map to this language (e.g., ["rs"] for Rust)
24    fn file_extensions(&self) -> &'static [&'static str];
25
26    /// The tree-sitter Language for this grammar
27    fn tree_sitter_language(&self) -> Language;
28
29    /// Highlight query (required)
30    fn highlights_query(&self) -> &'static str;
31
32    /// Fold query (optional)
33    fn folds_query(&self) -> Option<&'static str> {
34        None
35    }
36
37    /// Text objects query (optional)
38    fn textobjects_query(&self) -> Option<&'static str> {
39        None
40    }
41
42    /// Decorations query (optional, used by markdown)
43    fn decorations_query(&self) -> Option<&'static str> {
44        None
45    }
46
47    /// Injections query for embedded languages (optional)
48    fn injections_query(&self) -> Option<&'static str> {
49        None
50    }
51}
52
53/// A registered language with all its data
54pub struct RegisteredLanguage {
55    /// The language support implementation
56    pub support: Arc<dyn LanguageSupport>,
57    /// Compiled tree-sitter Language (cached)
58    language: Language,
59}
60
61impl RegisteredLanguage {
62    /// Create a new registered language
63    pub fn new(support: Arc<dyn LanguageSupport>) -> Self {
64        let language = support.tree_sitter_language();
65        Self { support, language }
66    }
67
68    /// Get the language ID
69    pub fn language_id(&self) -> &'static str {
70        self.support.language_id()
71    }
72
73    /// Get the tree-sitter Language
74    pub fn language(&self) -> &Language {
75        &self.language
76    }
77
78    /// Get file extensions
79    pub fn file_extensions(&self) -> &'static [&'static str] {
80        self.support.file_extensions()
81    }
82
83    /// Get highlights query
84    pub fn highlights_query(&self) -> &'static str {
85        self.support.highlights_query()
86    }
87
88    /// Get folds query
89    pub fn folds_query(&self) -> Option<&'static str> {
90        self.support.folds_query()
91    }
92
93    /// Get text objects query
94    pub fn textobjects_query(&self) -> Option<&'static str> {
95        self.support.textobjects_query()
96    }
97
98    /// Get decorations query
99    pub fn decorations_query(&self) -> Option<&'static str> {
100        self.support.decorations_query()
101    }
102
103    /// Get injections query
104    pub fn injections_query(&self) -> Option<&'static str> {
105        self.support.injections_query()
106    }
107}
108
109/// Registry for dynamically registered languages
110///
111/// Language plugins register themselves at startup, and the treesitter
112/// plugin uses this registry to detect languages and get their grammars.
113pub struct LanguageRegistry {
114    /// Registered languages by ID
115    languages: RwLock<HashMap<String, Arc<RegisteredLanguage>>>,
116    /// Extension to language ID mapping
117    extension_map: RwLock<HashMap<String, String>>,
118}
119
120impl Default for LanguageRegistry {
121    fn default() -> Self {
122        Self::new()
123    }
124}
125
126impl LanguageRegistry {
127    /// Create a new empty language registry
128    #[must_use]
129    pub fn new() -> Self {
130        Self {
131            languages: RwLock::new(HashMap::new()),
132            extension_map: RwLock::new(HashMap::new()),
133        }
134    }
135
136    /// Register a language support implementation
137    pub fn register(&self, support: Arc<dyn LanguageSupport>) {
138        let id = support.language_id().to_string();
139        let extensions = support.file_extensions();
140
141        let registered = Arc::new(RegisteredLanguage::new(support));
142
143        // Register in language map
144        self.languages
145            .write()
146            .unwrap()
147            .insert(id.clone(), registered);
148
149        // Register extensions
150        let mut ext_map = self.extension_map.write().unwrap();
151        for ext in extensions {
152            ext_map.insert((*ext).to_string(), id.clone());
153        }
154
155        tracing::debug!(language_id = %id, extensions = ?extensions, "Registered language");
156    }
157
158    /// Detect language from file path based on extension
159    #[must_use]
160    pub fn detect_language(&self, path: &str) -> Option<String> {
161        Path::new(path)
162            .extension()
163            .and_then(|e| e.to_str())
164            .and_then(|ext| self.extension_map.read().unwrap().get(ext).cloned())
165    }
166
167    /// Get a registered language by ID
168    #[must_use]
169    pub fn get(&self, id: &str) -> Option<Arc<RegisteredLanguage>> {
170        self.languages.read().unwrap().get(id).cloned()
171    }
172
173    /// Get the tree-sitter Language for a language ID
174    #[must_use]
175    pub fn get_language(&self, id: &str) -> Option<Language> {
176        self.languages
177            .read()
178            .unwrap()
179            .get(id)
180            .map(|l| l.language().clone())
181    }
182
183    /// Check if a language is registered
184    #[must_use]
185    pub fn is_registered(&self, id: &str) -> bool {
186        self.languages.read().unwrap().contains_key(id)
187    }
188
189    /// Get all registered language IDs
190    #[must_use]
191    pub fn language_ids(&self) -> Vec<String> {
192        self.languages.read().unwrap().keys().cloned().collect()
193    }
194}