fresh/services/plugins/
manager.rs1use crate::config_io::DirectoryContext;
9use crate::input::command_registry::CommandRegistry;
10use fresh_core::config::PluginConfig;
11use std::collections::HashMap;
12use std::path::Path;
13use std::sync::{Arc, RwLock};
14
15#[cfg(feature = "plugins")]
16use super::bridge::EditorServiceBridge;
17#[cfg(feature = "plugins")]
18use fresh_plugin_runtime::PluginThreadHandle;
19
20pub struct PluginManager {
25 #[cfg(feature = "plugins")]
26 inner: Option<PluginThreadHandle>,
27 #[cfg(not(feature = "plugins"))]
28 _phantom: std::marker::PhantomData<()>,
29}
30
31impl PluginManager {
32 pub fn new(
37 enable: bool,
38 command_registry: Arc<RwLock<CommandRegistry>>,
39 dir_context: DirectoryContext,
40 theme_cache: Arc<RwLock<HashMap<String, serde_json::Value>>>,
41 ) -> Self {
42 #[cfg(feature = "plugins")]
43 {
44 if enable {
45 let services = Arc::new(EditorServiceBridge {
46 command_registry: command_registry.clone(),
47 dir_context,
48 theme_cache,
49 });
50 match PluginThreadHandle::spawn(services) {
51 Ok(handle) => {
52 return Self {
53 inner: Some(handle),
54 }
55 }
56 Err(e) => {
57 tracing::error!("Failed to spawn TypeScript plugin thread: {}", e);
58 #[cfg(debug_assertions)]
59 panic!("TypeScript plugin thread creation failed: {}", e);
60 }
61 }
62 } else {
63 tracing::info!("Plugins disabled via --no-plugins flag");
64 }
65 Self { inner: None }
66 }
67
68 #[cfg(not(feature = "plugins"))]
69 {
70 let _ = command_registry; let _ = dir_context; let _ = theme_cache; if enable {
74 tracing::warn!("Plugins requested but compiled without plugin support");
75 }
76 Self {
77 _phantom: std::marker::PhantomData,
78 }
79 }
80 }
81
82 pub fn is_active(&self) -> bool {
84 #[cfg(feature = "plugins")]
85 {
86 self.inner.is_some()
87 }
88 #[cfg(not(feature = "plugins"))]
89 {
90 false
91 }
92 }
93
94 pub fn is_alive(&self) -> bool {
96 #[cfg(feature = "plugins")]
97 {
98 self.inner.as_ref().map(|h| h.is_alive()).unwrap_or(false)
99 }
100 #[cfg(not(feature = "plugins"))]
101 {
102 false
103 }
104 }
105
106 pub fn check_thread_health(&mut self) {
110 #[cfg(feature = "plugins")]
111 {
112 if let Some(ref mut handle) = self.inner {
113 handle.check_thread_health();
114 }
115 }
116 }
117
118 pub fn load_plugins_from_dir(&self, dir: &Path) -> Vec<String> {
120 #[cfg(feature = "plugins")]
121 {
122 if let Some(ref manager) = self.inner {
123 return manager.load_plugins_from_dir(dir);
124 }
125 Vec::new()
126 }
127 #[cfg(not(feature = "plugins"))]
128 {
129 let _ = dir;
130 Vec::new()
131 }
132 }
133
134 #[cfg(feature = "plugins")]
138 pub fn load_plugins_from_dir_with_config(
139 &self,
140 dir: &Path,
141 plugin_configs: &HashMap<String, PluginConfig>,
142 ) -> (Vec<String>, HashMap<String, PluginConfig>) {
143 if let Some(ref manager) = self.inner {
144 return manager.load_plugins_from_dir_with_config(dir, plugin_configs);
145 }
146 (Vec::new(), HashMap::new())
147 }
148
149 #[cfg(not(feature = "plugins"))]
151 pub fn load_plugins_from_dir_with_config(
152 &self,
153 dir: &Path,
154 plugin_configs: &HashMap<String, PluginConfig>,
155 ) -> (Vec<String>, HashMap<String, PluginConfig>) {
156 let _ = (dir, plugin_configs);
157 (Vec::new(), HashMap::new())
158 }
159
160 pub fn unload_plugin(&self, name: &str) -> anyhow::Result<()> {
162 #[cfg(feature = "plugins")]
163 {
164 self.inner
165 .as_ref()
166 .ok_or_else(|| anyhow::anyhow!("Plugin system not active"))?
167 .unload_plugin(name)
168 }
169 #[cfg(not(feature = "plugins"))]
170 {
171 let _ = name;
172 Ok(())
173 }
174 }
175
176 pub fn load_plugin(&self, path: &Path) -> anyhow::Result<()> {
178 #[cfg(feature = "plugins")]
179 {
180 self.inner
181 .as_ref()
182 .ok_or_else(|| anyhow::anyhow!("Plugin system not active"))?
183 .load_plugin(path)
184 }
185 #[cfg(not(feature = "plugins"))]
186 {
187 let _ = path;
188 Ok(())
189 }
190 }
191
192 pub fn load_plugin_from_source(
197 &self,
198 source: &str,
199 name: &str,
200 is_typescript: bool,
201 ) -> anyhow::Result<()> {
202 #[cfg(feature = "plugins")]
203 {
204 self.inner
205 .as_ref()
206 .ok_or_else(|| anyhow::anyhow!("Plugin system not active"))?
207 .load_plugin_from_source(source, name, is_typescript)
208 }
209 #[cfg(not(feature = "plugins"))]
210 {
211 let _ = (source, name, is_typescript);
212 Ok(())
213 }
214 }
215
216 pub fn run_hook(&self, hook_name: &str, args: super::hooks::HookArgs) {
218 #[cfg(feature = "plugins")]
219 {
220 if let Some(ref manager) = self.inner {
221 manager.run_hook(hook_name, args);
222 }
223 }
224 #[cfg(not(feature = "plugins"))]
225 {
226 let _ = (hook_name, args);
227 }
228 }
229
230 pub fn deliver_response(&self, response: super::api::PluginResponse) {
232 #[cfg(feature = "plugins")]
233 {
234 if let Some(ref manager) = self.inner {
235 manager.deliver_response(response);
236 }
237 }
238 #[cfg(not(feature = "plugins"))]
239 {
240 let _ = response;
241 }
242 }
243
244 pub fn process_commands(&mut self) -> Vec<super::api::PluginCommand> {
246 #[cfg(feature = "plugins")]
247 {
248 if let Some(ref mut manager) = self.inner {
249 return manager.process_commands();
250 }
251 Vec::new()
252 }
253 #[cfg(not(feature = "plugins"))]
254 {
255 Vec::new()
256 }
257 }
258
259 pub fn process_commands_until_hook_completed(
267 &mut self,
268 hook_name: &str,
269 timeout: std::time::Duration,
270 ) -> Vec<super::api::PluginCommand> {
271 #[cfg(feature = "plugins")]
272 {
273 if let Some(ref mut manager) = self.inner {
274 return manager.process_commands_until_hook_completed(hook_name, timeout);
275 }
276 Vec::new()
277 }
278 #[cfg(not(feature = "plugins"))]
279 {
280 let _ = (hook_name, timeout);
281 Vec::new()
282 }
283 }
284
285 #[cfg(feature = "plugins")]
287 pub fn state_snapshot_handle(&self) -> Option<Arc<RwLock<super::api::EditorStateSnapshot>>> {
288 self.inner.as_ref().map(|m| m.state_snapshot_handle())
289 }
290
291 #[cfg(feature = "plugins")]
293 pub fn execute_action_async(
294 &self,
295 action_name: &str,
296 ) -> Option<anyhow::Result<fresh_plugin_runtime::thread::oneshot::Receiver<anyhow::Result<()>>>>
297 {
298 self.inner
299 .as_ref()
300 .map(|m| m.execute_action_async(action_name))
301 }
302
303 #[cfg(feature = "plugins")]
305 pub fn list_plugins(
306 &self,
307 ) -> Vec<fresh_plugin_runtime::backend::quickjs_backend::TsPluginInfo> {
308 self.inner
309 .as_ref()
310 .map(|m| m.list_plugins())
311 .unwrap_or_default()
312 }
313
314 #[cfg(feature = "plugins")]
316 pub fn reload_plugin(&self, name: &str) -> anyhow::Result<()> {
317 self.inner
318 .as_ref()
319 .ok_or_else(|| anyhow::anyhow!("Plugin system not active"))?
320 .reload_plugin(name)
321 }
322
323 pub fn has_hook_handlers(&self, hook_name: &str) -> bool {
325 #[cfg(feature = "plugins")]
326 {
327 self.inner
328 .as_ref()
329 .map(|m| m.has_hook_handlers(hook_name))
330 .unwrap_or(false)
331 }
332 #[cfg(not(feature = "plugins"))]
333 {
334 let _ = hook_name;
335 false
336 }
337 }
338
339 #[cfg(feature = "plugins")]
341 pub fn resolve_callback(&self, callback_id: super::api::JsCallbackId, result_json: String) {
342 if let Some(inner) = &self.inner {
343 inner.resolve_callback(callback_id, result_json);
344 }
345 }
346
347 #[cfg(not(feature = "plugins"))]
349 pub fn resolve_callback(
350 &self,
351 callback_id: fresh_core::api::JsCallbackId,
352 result_json: String,
353 ) {
354 let _ = (callback_id, result_json);
355 }
356
357 #[cfg(feature = "plugins")]
359 pub fn reject_callback(&self, callback_id: super::api::JsCallbackId, error: String) {
360 if let Some(inner) = &self.inner {
361 inner.reject_callback(callback_id, error);
362 }
363 }
364
365 #[cfg(not(feature = "plugins"))]
367 pub fn reject_callback(&self, callback_id: fresh_core::api::JsCallbackId, error: String) {
368 let _ = (callback_id, error);
369 }
370
371 #[cfg(feature = "plugins")]
374 pub fn call_streaming_callback(
375 &self,
376 callback_id: fresh_core::api::JsCallbackId,
377 result_json: String,
378 done: bool,
379 ) {
380 if let Some(inner) = &self.inner {
381 inner.call_streaming_callback(callback_id, result_json, done);
382 }
383 }
384
385 #[cfg(not(feature = "plugins"))]
387 pub fn call_streaming_callback(
388 &self,
389 callback_id: fresh_core::api::JsCallbackId,
390 result_json: String,
391 done: bool,
392 ) {
393 let _ = (callback_id, result_json, done);
394 }
395}