reovim_plugin_context/
lib.rs

1//! Context plugin for reovim
2//!
3//! Provides event-driven context updates for UI components.
4//! Instead of consumers polling for context on every frame, this plugin:
5//!
6//! 1. Subscribes to core events (`CursorMoved`, `ViewportScrolled`, `BufferModified`)
7//! 2. Computes context using the registered `ContextProvider`
8//! 3. Emits context update events (`CursorContextUpdated`, `ViewportContextUpdated`)
9//! 4. Consumers subscribe to these events and cache the results
10//!
11//! This reduces redundant computation and improves performance.
12
13pub mod events;
14pub mod manager;
15
16use std::sync::Arc;
17
18pub use {
19    events::{CursorContextUpdated, ViewportContextUpdated},
20    manager::{CachedContext, ContextManager, SharedContextManager},
21};
22
23use reovim_core::{
24    event_bus::{BufferModified, CursorMoved, EventBus, EventResult, ViewportScrolled},
25    plugin::{Plugin, PluginContext, PluginId, PluginStateRegistry},
26};
27
28/// Context plugin for event-driven context updates
29pub struct ContextPlugin {
30    manager: SharedContextManager,
31}
32
33impl Default for ContextPlugin {
34    fn default() -> Self {
35        Self::new()
36    }
37}
38
39impl ContextPlugin {
40    /// Create a new context plugin
41    #[must_use]
42    pub fn new() -> Self {
43        Self {
44            manager: Arc::new(ContextManager::new()),
45        }
46    }
47}
48
49impl Plugin for ContextPlugin {
50    fn id(&self) -> PluginId {
51        PluginId::new("reovim:context")
52    }
53
54    fn name(&self) -> &'static str {
55        "Context"
56    }
57
58    fn description(&self) -> &'static str {
59        "Event-driven context updates for UI components"
60    }
61
62    fn build(&self, _ctx: &mut PluginContext) {
63        tracing::debug!("ContextPlugin: built");
64    }
65
66    fn init_state(&self, registry: &PluginStateRegistry) {
67        // Register shared context manager for other plugins to access cached context
68        registry.register(Arc::clone(&self.manager));
69        tracing::debug!("ContextPlugin: initialized SharedContextManager");
70    }
71
72    fn subscribe(&self, bus: &EventBus, state: Arc<PluginStateRegistry>) {
73        // Handle cursor movement - compute and emit cursor context
74        let manager = Arc::clone(&self.manager);
75        let state_for_cursor = Arc::clone(&state);
76        bus.subscribe::<CursorMoved, _>(100, move |event, ctx| {
77            let buffer_id = event.buffer_id;
78            #[allow(clippy::cast_possible_truncation)]
79            let line = event.to.0 as u32;
80            #[allow(clippy::cast_possible_truncation)]
81            let col = event.to.1 as u32;
82
83            // Check if cursor actually changed position
84            if !manager.cursor_changed(buffer_id, line, col) {
85                return EventResult::Handled;
86            }
87            manager.update_cursor(buffer_id, line, col);
88
89            // Get context from the registry's context provider
90            // Note: content is cached in treesitter manager, so we pass empty string
91            // The provider will use its own cached source
92            let context = state_for_cursor.get_context(buffer_id, line, col, "");
93
94            // Cache the result
95            manager.set_cursor_context(CachedContext {
96                buffer_id,
97                line,
98                col,
99                context: context.clone(),
100            });
101
102            // Emit context update event
103            ctx.emit(CursorContextUpdated {
104                buffer_id,
105                line,
106                col,
107                context,
108            });
109
110            tracing::trace!(buffer_id, line, col, "ContextPlugin: emitted CursorContextUpdated");
111
112            EventResult::Handled
113        });
114
115        // Handle viewport scroll - compute and emit viewport context
116        let manager = Arc::clone(&self.manager);
117        let state_for_viewport = Arc::clone(&state);
118        bus.subscribe::<ViewportScrolled, _>(100, move |event, ctx| {
119            let buffer_id = event.buffer_id;
120            let top_line = event.top_line;
121
122            tracing::debug!(
123                buffer_id,
124                top_line,
125                window_id = event.window_id,
126                "ContextPlugin: received ViewportScrolled"
127            );
128
129            // Check if viewport actually changed
130            if !manager.viewport_changed(buffer_id, top_line) {
131                tracing::debug!(buffer_id, top_line, "ContextPlugin: viewport unchanged, skipping");
132                return EventResult::Handled;
133            }
134            manager.update_viewport(buffer_id, top_line);
135
136            // Get context at the top line of the viewport
137            // This gives us the enclosing scopes that should be shown as sticky headers
138            let context = state_for_viewport.get_context(buffer_id, top_line, 0, "");
139            let has_context = context.is_some();
140
141            // Cache the result
142            manager.set_viewport_context(CachedContext {
143                buffer_id,
144                line: top_line,
145                col: 0,
146                context: context.clone(),
147            });
148
149            // Emit context update event
150            ctx.emit(ViewportContextUpdated {
151                window_id: event.window_id,
152                buffer_id,
153                top_line,
154                context,
155            });
156
157            tracing::debug!(
158                buffer_id,
159                top_line,
160                window_id = event.window_id,
161                has_context,
162                "ContextPlugin: emitted ViewportContextUpdated"
163            );
164
165            EventResult::Handled
166        });
167
168        // Handle buffer modification - invalidate cached context
169        let manager = Arc::clone(&self.manager);
170        bus.subscribe::<BufferModified, _>(100, move |event, _ctx| {
171            manager.invalidate_buffer(event.buffer_id);
172            tracing::trace!(
173                buffer_id = event.buffer_id,
174                "ContextPlugin: invalidated context cache"
175            );
176            EventResult::Handled
177        });
178
179        tracing::debug!("ContextPlugin: subscribed to cursor, viewport, and buffer events");
180    }
181}