reovim_plugin_treesitter/
lib.rs

1//! Treesitter plugin for reovim
2//!
3//! Provides syntax highlighting, code folding, and semantic text objects
4//! via tree-sitter parsing. Language support is provided by separate
5//! language plugins that register with this infrastructure plugin.
6
7use std::sync::Arc;
8
9use {
10    reovim_core::{
11        event::RuntimeEvent,
12        event_bus::{EventBus, EventResult, FileOpened},
13        plugin::{Plugin, PluginContext, PluginId, PluginStateRegistry},
14    },
15    tokio::sync::mpsc,
16};
17
18pub mod command;
19pub mod context;
20pub mod edit;
21pub mod events;
22pub mod factory;
23pub mod highlighter;
24pub mod injection;
25pub mod manager;
26pub mod parser;
27pub mod queries;
28pub mod registry;
29pub mod state;
30pub mod syntax;
31pub mod text_objects;
32pub mod theme;
33
34pub use {
35    command::{JumpToNextScope, JumpToParentScope, JumpToPrevScope},
36    context::TreesitterContextProvider,
37    edit::BufferEdit,
38    events::{
39        HighlightsReady, ParseCompleted, ParseRequest, RegisterLanguage, TreesitterFoldRanges,
40    },
41    highlighter::Highlighter,
42    injection::{InjectionDetector, InjectionLayer, InjectionManager, InjectionRegion},
43    manager::TreesitterManager,
44    parser::BufferParser,
45    queries::{QueryCache, QueryType},
46    registry::{LanguageRegistry, LanguageSupport, RegisteredLanguage},
47    state::SharedTreesitterManager,
48    theme::TreesitterTheme,
49};
50
51/// Re-export tree-sitter types for language plugins
52pub use tree_sitter::Language;
53
54/// Treesitter infrastructure plugin
55///
56/// Manages tree-sitter parsing and highlighting for all buffers.
57/// Language support is provided dynamically by language plugins
58/// that emit `RegisterLanguage` events.
59pub struct TreesitterPlugin {
60    manager: Arc<SharedTreesitterManager>,
61}
62
63impl Default for TreesitterPlugin {
64    fn default() -> Self {
65        Self::new()
66    }
67}
68
69impl TreesitterPlugin {
70    /// Create a new treesitter plugin
71    pub fn new() -> Self {
72        Self {
73            manager: Arc::new(SharedTreesitterManager::new()),
74        }
75    }
76}
77
78impl Plugin for TreesitterPlugin {
79    fn id(&self) -> PluginId {
80        PluginId::new("reovim:treesitter")
81    }
82
83    fn name(&self) -> &'static str {
84        "Treesitter"
85    }
86
87    fn description(&self) -> &'static str {
88        "Syntax highlighting and semantic analysis via tree-sitter"
89    }
90
91    fn build(&self, ctx: &mut PluginContext) {
92        use reovim_core::{
93            bind::{CommandRef, KeymapScope},
94            command::id::CommandId,
95            keys,
96        };
97
98        // Register navigation commands
99        let _ = ctx.register_command(command::JumpToParentScope);
100        let _ = ctx.register_command(command::JumpToPrevScope);
101        let _ = ctx.register_command(command::JumpToNextScope);
102
103        // Bind keys in normal mode
104        ctx.bind_key_scoped(
105            KeymapScope::editor_normal(),
106            keys!['g' 'u'],
107            CommandRef::Registered(CommandId::new("jump_to_parent_scope")),
108        );
109
110        ctx.bind_key_scoped(
111            KeymapScope::editor_normal(),
112            keys!['[' 's'],
113            CommandRef::Registered(CommandId::new("jump_to_prev_scope")),
114        );
115
116        ctx.bind_key_scoped(
117            KeymapScope::editor_normal(),
118            keys![']' 's'],
119            CommandRef::Registered(CommandId::new("jump_to_next_scope")),
120        );
121
122        tracing::debug!("TreesitterPlugin: registered render stage and navigation commands");
123    }
124
125    fn init_state(&self, registry: &PluginStateRegistry) {
126        // Register as the semantic text object source
127        registry.set_text_object_source(Arc::clone(&self.manager) as _);
128
129        // Store in plugin state registry for other plugins to access
130        registry.register(Arc::clone(&self.manager));
131
132        // Register the syntax factory for buffer-centric highlighting
133        let factory =
134            Arc::new(crate::factory::TreesitterSyntaxFactory::new(Arc::clone(&self.manager)));
135        registry.set_syntax_factory(factory);
136
137        // Register the context provider for scope detection
138        let context_provider =
139            Arc::new(crate::context::TreesitterContextProvider::new(Arc::clone(&self.manager)));
140        registry.register_context_provider(context_provider);
141
142        tracing::debug!(
143            "TreesitterPlugin: initialized state with syntax factory and context provider"
144        );
145    }
146
147    fn subscribe(&self, bus: &EventBus, state: Arc<PluginStateRegistry>) {
148        // Handle language registration from language plugins
149        {
150            let state = Arc::clone(&state);
151            bus.subscribe::<RegisterLanguage, _>(100, move |event, ctx| {
152                state.with_mut::<Arc<SharedTreesitterManager>, _, _>(|manager| {
153                    manager.with_mut(|m| {
154                        m.register_language(Arc::clone(&event.language));
155                    });
156                    tracing::info!(
157                        language_id = %event.language.language_id(),
158                        "Registered language with treesitter"
159                    );
160                });
161                // Trigger re-render so injection system can create layers for this language
162                ctx.request_render();
163                EventResult::Handled
164            });
165        }
166
167        // Handle file open - detect language and init parser
168        {
169            let state = Arc::clone(&state);
170            bus.subscribe::<FileOpened, _>(100, move |event, _ctx| {
171                state.with_mut::<Arc<SharedTreesitterManager>, _, _>(|manager| {
172                    let language_id =
173                        manager.with_mut(|m| m.init_buffer(event.buffer_id, Some(&event.path)));
174
175                    if let Some(lang_id) = language_id {
176                        tracing::debug!(
177                            buffer_id = event.buffer_id,
178                            language_id = %lang_id,
179                            "Initialized treesitter for buffer"
180                        );
181                    }
182                });
183                EventResult::Handled
184            });
185        }
186
187        // Handle buffer close - cleanup parser state
188        {
189            use reovim_core::event_bus::BufferClosed;
190            let state = Arc::clone(&state);
191            bus.subscribe::<BufferClosed, _>(100, move |event, _ctx| {
192                state.with_mut::<Arc<SharedTreesitterManager>, _, _>(|manager| {
193                    manager.with_mut(|m| {
194                        m.remove_buffer(event.buffer_id);
195                    });
196                });
197                EventResult::Handled
198            });
199        }
200
201        // Handle buffer modifications - schedule reparse
202        {
203            use reovim_core::event_bus::BufferModified;
204            let state = Arc::clone(&state);
205            bus.subscribe::<BufferModified, _>(100, move |event, _ctx| {
206                state.with_mut::<Arc<SharedTreesitterManager>, _, _>(|manager| {
207                    manager.with_mut(|m| {
208                        if m.has_parser(event.buffer_id) {
209                            m.schedule_reparse(event.buffer_id);
210                        }
211                    });
212                });
213                EventResult::Handled
214            });
215        }
216
217        // Handle parse requests
218        {
219            let state = Arc::clone(&state);
220            bus.subscribe::<ParseRequest, _>(100, move |event, ctx| {
221                let result = state.with_mut::<Arc<SharedTreesitterManager>, _, _>(|manager| {
222                    let (language_id, has_parser) = manager.with(|m| {
223                        (m.buffer_language(event.buffer_id), m.has_parser(event.buffer_id))
224                    });
225
226                    if has_parser { language_id } else { None }
227                });
228
229                if let Some(Some(lang_id)) = result {
230                    ctx.emit(ParseCompleted {
231                        buffer_id: event.buffer_id,
232                        language_id: lang_id,
233                    });
234                }
235                EventResult::Handled
236            });
237        }
238
239        // === Navigation command handlers ===
240
241        // Jump to parent scope
242        {
243            let _state = Arc::clone(&state);
244            bus.subscribe::<command::JumpToParentScope, _>(100, move |_event, _ctx| {
245                // TODO: Implement full parent scope navigation
246                // Need to:
247                // 1. Get active buffer ID and cursor position from runtime state
248                // 2. Get buffer content
249                // 3. Query context from provider registry
250                // 4. Find parent scope (second-to-last item)
251                // 5. Emit RequestCursorMove to parent start line
252                EventResult::NotHandled
253            });
254        }
255
256        // Jump to previous scope
257        {
258            let _state = Arc::clone(&state);
259            bus.subscribe::<command::JumpToPrevScope, _>(100, move |_event, _ctx| {
260                // TODO: Implement - find nearest scope header above cursor
261                EventResult::NotHandled
262            });
263        }
264
265        // Jump to next scope
266        {
267            let _state = Arc::clone(&state);
268            bus.subscribe::<command::JumpToNextScope, _>(100, move |_event, _ctx| {
269                // TODO: Implement - find nearest scope header below cursor
270                EventResult::NotHandled
271            });
272        }
273
274        tracing::debug!("TreesitterPlugin: subscribed to events");
275    }
276
277    fn boot(
278        &self,
279        _bus: &EventBus,
280        _state: Arc<PluginStateRegistry>,
281        _event_tx: Option<mpsc::Sender<RuntimeEvent>>,
282    ) {
283        // Spawn background thread to pre-compile all queries
284        // This runs after all languages are registered, before first file opens
285        let manager = Arc::clone(&self.manager);
286        std::thread::spawn(move || {
287            let start = std::time::Instant::now();
288            let count = manager.with(|m| m.precompile_all_queries());
289            let elapsed = start.elapsed();
290            tracing::info!(
291                queries = count,
292                elapsed_ms = elapsed.as_millis(),
293                "Background query precompilation finished"
294            );
295        });
296        tracing::debug!("TreesitterPlugin: spawned background query precompilation");
297    }
298}