reovim_plugin_fold/
lib.rs

1//! Code folding plugin for reovim
2//!
3//! This plugin provides code folding functionality:
4//! - Toggle, open, close folds at cursor
5//! - Open/close all folds in buffer
6//! - Fold ranges from treesitter queries
7//!
8//! # State Management
9//!
10//! The plugin stores `FoldManager` in `PluginStateRegistry`, accessible by any component.
11//!
12//! # Event Bus Integration
13//!
14//! Commands emit fold events (unified types), and the plugin subscribes to handle them.
15
16pub mod commands;
17pub mod stage;
18pub mod state;
19
20#[cfg(test)]
21mod tests;
22
23use std::{any::TypeId, sync::Arc};
24
25use reovim_core::{
26    event_bus::{BufferClosed, EventBus, EventResult, FoldRangesComputed},
27    plugin::{Plugin, PluginContext, PluginId, PluginStateRegistry},
28};
29
30use {stage::FoldRenderStage, state::SharedFoldManager};
31
32// Import unified command-event types for internal use and re-export
33pub use commands::{FoldClose, FoldCloseAll, FoldOpen, FoldOpenAll, FoldRangesUpdated, FoldToggle};
34
35/// Code folding plugin
36///
37/// Provides code folding with treesitter integration:
38/// - `za` toggle fold
39/// - `zo` open fold
40/// - `zc` close fold
41/// - `zR` open all folds
42/// - `zM` close all folds
43pub struct FoldPlugin {
44    fold_manager: Arc<SharedFoldManager>,
45}
46
47impl Default for FoldPlugin {
48    fn default() -> Self {
49        Self::new()
50    }
51}
52
53impl FoldPlugin {
54    /// Create a new fold plugin
55    #[must_use]
56    pub fn new() -> Self {
57        Self {
58            fold_manager: Arc::new(SharedFoldManager::new()),
59        }
60    }
61}
62
63impl Plugin for FoldPlugin {
64    fn id(&self) -> PluginId {
65        PluginId::new("reovim:fold")
66    }
67
68    fn name(&self) -> &'static str {
69        "Fold"
70    }
71
72    fn description(&self) -> &'static str {
73        "Code folding with treesitter integration"
74    }
75
76    fn dependencies(&self) -> Vec<TypeId> {
77        // CorePlugin dependency
78        vec![]
79    }
80
81    fn build(&self, ctx: &mut PluginContext) {
82        // Register commands (unified types)
83        let _ = ctx.register_command(FoldToggle::default_instance());
84        let _ = ctx.register_command(FoldOpen::default_instance());
85        let _ = ctx.register_command(FoldClose::default_instance());
86        let _ = ctx.register_command(FoldOpenAll::default_instance());
87        let _ = ctx.register_command(FoldCloseAll::default_instance());
88
89        // Register render stage for fold visibility transformations
90        let stage = Arc::new(FoldRenderStage::new(Arc::clone(&self.fold_manager)));
91        ctx.register_render_stage(stage);
92
93        tracing::debug!("FoldPlugin: registered commands and render stage");
94    }
95
96    fn init_state(&self, registry: &PluginStateRegistry) {
97        // Register shared fold manager as both type state and visibility source
98        registry.register(Arc::clone(&self.fold_manager));
99        registry.set_visibility_source(Arc::clone(&self.fold_manager)
100            as Arc<dyn reovim_core::visibility::BufferVisibilitySource>);
101
102        tracing::debug!("FoldPlugin: initialized SharedFoldManager");
103    }
104
105    fn subscribe(&self, bus: &EventBus, state: Arc<PluginStateRegistry>) {
106        // Handle fold toggle (unified type)
107        let state_clone = Arc::clone(&state);
108        bus.subscribe::<FoldToggle, _>(100, move |event, _ctx| {
109            state_clone.with::<Arc<SharedFoldManager>, _, _>(|fm| {
110                fm.with_mut(|m| m.toggle(event.buffer_id, event.line));
111            });
112            tracing::trace!(
113                buffer_id = event.buffer_id,
114                line = event.line,
115                "FoldPlugin: toggled fold"
116            );
117            EventResult::Handled
118        });
119
120        // Handle fold open (unified type)
121        let state_clone = Arc::clone(&state);
122        bus.subscribe::<FoldOpen, _>(100, move |event, _ctx| {
123            state_clone.with::<Arc<SharedFoldManager>, _, _>(|fm| {
124                fm.with_mut(|m| m.open(event.buffer_id, event.line));
125            });
126            tracing::trace!(
127                buffer_id = event.buffer_id,
128                line = event.line,
129                "FoldPlugin: opened fold"
130            );
131            EventResult::Handled
132        });
133
134        // Handle fold close (unified type)
135        let state_clone = Arc::clone(&state);
136        bus.subscribe::<FoldClose, _>(100, move |event, _ctx| {
137            state_clone.with::<Arc<SharedFoldManager>, _, _>(|fm| {
138                fm.with_mut(|m| m.close(event.buffer_id, event.line));
139            });
140            tracing::trace!(
141                buffer_id = event.buffer_id,
142                line = event.line,
143                "FoldPlugin: closed fold"
144            );
145            EventResult::Handled
146        });
147
148        // Handle open all folds (unified type)
149        let state_clone = Arc::clone(&state);
150        bus.subscribe::<FoldOpenAll, _>(100, move |event, _ctx| {
151            state_clone.with::<Arc<SharedFoldManager>, _, _>(|fm| {
152                fm.with_mut(|m| m.open_all(event.buffer_id));
153            });
154            tracing::trace!(buffer_id = event.buffer_id, "FoldPlugin: opened all folds");
155            EventResult::Handled
156        });
157
158        // Handle close all folds (unified type)
159        let state_clone = Arc::clone(&state);
160        bus.subscribe::<FoldCloseAll, _>(100, move |event, _ctx| {
161            state_clone.with::<Arc<SharedFoldManager>, _, _>(|fm| {
162                fm.with_mut(|m| m.close_all(event.buffer_id));
163            });
164            tracing::trace!(buffer_id = event.buffer_id, "FoldPlugin: closed all folds");
165            EventResult::Handled
166        });
167
168        // Handle fold ranges update (from plugin events)
169        let state_clone = Arc::clone(&state);
170        bus.subscribe::<FoldRangesUpdated, _>(50, move |event, _ctx| {
171            state_clone.with::<Arc<SharedFoldManager>, _, _>(|fm| {
172                fm.with_mut(|m| m.set_ranges(event.buffer_id, event.ranges.clone()));
173            });
174            tracing::trace!(
175                buffer_id = event.buffer_id,
176                count = event.ranges.len(),
177                "FoldPlugin: updated fold ranges (plugin event)"
178            );
179            EventResult::Handled
180        });
181
182        // Handle fold ranges computed by treesitter (core event)
183        let state_clone = Arc::clone(&state);
184        bus.subscribe::<FoldRangesComputed, _>(50, move |event, _ctx| {
185            state_clone.with::<Arc<SharedFoldManager>, _, _>(|fm| {
186                fm.with_mut(|m| m.set_ranges(event.buffer_id, event.ranges.clone()));
187            });
188            tracing::trace!(
189                buffer_id = event.buffer_id,
190                count = event.ranges.len(),
191                "FoldPlugin: updated fold ranges (treesitter)"
192            );
193            EventResult::Handled
194        });
195
196        // Handle buffer close - clean up fold state
197        let state_clone = Arc::clone(&state);
198        bus.subscribe::<BufferClosed, _>(100, move |event, _ctx| {
199            state_clone.with::<Arc<SharedFoldManager>, _, _>(|fm| {
200                fm.with_mut(|m| m.remove_buffer(event.buffer_id));
201            });
202            tracing::trace!(
203                buffer_id = event.buffer_id,
204                "FoldPlugin: cleaned up fold state for closed buffer"
205            );
206            EventResult::Handled
207        });
208
209        tracing::debug!("FoldPlugin: subscribed to fold events");
210    }
211}
212
213// Re-export fold types
214// Data types (FoldKind, FoldRange) come from core (used by treesitter)
215// State types (FoldState, FoldManager, SharedFoldManager) are plugin-owned
216pub use {
217    reovim_core::folding::{FoldKind, FoldRange},
218    state::{FoldManager, FoldState},
219};
220// SharedFoldManager is already imported at the top for internal use