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 ) -> Self {
41 #[cfg(feature = "plugins")]
42 {
43 if enable {
44 let services = Arc::new(EditorServiceBridge {
45 command_registry: command_registry.clone(),
46 dir_context,
47 });
48 match PluginThreadHandle::spawn(services) {
49 Ok(handle) => {
50 return Self {
51 inner: Some(handle),
52 }
53 }
54 Err(e) => {
55 tracing::error!("Failed to spawn TypeScript plugin thread: {}", e);
56 #[cfg(debug_assertions)]
57 panic!("TypeScript plugin thread creation failed: {}", e);
58 }
59 }
60 } else {
61 tracing::info!("Plugins disabled via --no-plugins flag");
62 }
63 Self { inner: None }
64 }
65
66 #[cfg(not(feature = "plugins"))]
67 {
68 let _ = command_registry; let _ = dir_context; if enable {
71 tracing::warn!("Plugins requested but compiled without plugin support");
72 }
73 Self {
74 _phantom: std::marker::PhantomData,
75 }
76 }
77 }
78
79 pub fn is_active(&self) -> bool {
81 #[cfg(feature = "plugins")]
82 {
83 self.inner.is_some()
84 }
85 #[cfg(not(feature = "plugins"))]
86 {
87 false
88 }
89 }
90
91 pub fn is_alive(&self) -> bool {
93 #[cfg(feature = "plugins")]
94 {
95 self.inner.as_ref().map(|h| h.is_alive()).unwrap_or(false)
96 }
97 #[cfg(not(feature = "plugins"))]
98 {
99 false
100 }
101 }
102
103 pub fn check_thread_health(&mut self) {
107 #[cfg(feature = "plugins")]
108 {
109 if let Some(ref mut handle) = self.inner {
110 handle.check_thread_health();
111 }
112 }
113 }
114
115 pub fn load_plugins_from_dir(&self, dir: &Path) -> Vec<String> {
117 #[cfg(feature = "plugins")]
118 {
119 if let Some(ref manager) = self.inner {
120 return manager.load_plugins_from_dir(dir);
121 }
122 Vec::new()
123 }
124 #[cfg(not(feature = "plugins"))]
125 {
126 let _ = dir;
127 Vec::new()
128 }
129 }
130
131 #[cfg(feature = "plugins")]
135 pub fn load_plugins_from_dir_with_config(
136 &self,
137 dir: &Path,
138 plugin_configs: &HashMap<String, PluginConfig>,
139 ) -> (Vec<String>, HashMap<String, PluginConfig>) {
140 if let Some(ref manager) = self.inner {
141 return manager.load_plugins_from_dir_with_config(dir, plugin_configs);
142 }
143 (Vec::new(), HashMap::new())
144 }
145
146 #[cfg(not(feature = "plugins"))]
148 pub fn load_plugins_from_dir_with_config(
149 &self,
150 dir: &Path,
151 plugin_configs: &HashMap<String, PluginConfig>,
152 ) -> (Vec<String>, HashMap<String, PluginConfig>) {
153 let _ = (dir, plugin_configs);
154 (Vec::new(), HashMap::new())
155 }
156
157 pub fn unload_plugin(&self, name: &str) -> anyhow::Result<()> {
159 #[cfg(feature = "plugins")]
160 {
161 self.inner
162 .as_ref()
163 .ok_or_else(|| anyhow::anyhow!("Plugin system not active"))?
164 .unload_plugin(name)
165 }
166 #[cfg(not(feature = "plugins"))]
167 {
168 let _ = name;
169 Ok(())
170 }
171 }
172
173 pub fn load_plugin(&self, path: &Path) -> anyhow::Result<()> {
175 #[cfg(feature = "plugins")]
176 {
177 self.inner
178 .as_ref()
179 .ok_or_else(|| anyhow::anyhow!("Plugin system not active"))?
180 .load_plugin(path)
181 }
182 #[cfg(not(feature = "plugins"))]
183 {
184 let _ = path;
185 Ok(())
186 }
187 }
188
189 pub fn run_hook(&self, hook_name: &str, args: super::hooks::HookArgs) {
191 #[cfg(feature = "plugins")]
192 {
193 if let Some(ref manager) = self.inner {
194 manager.run_hook(hook_name, args);
195 }
196 }
197 #[cfg(not(feature = "plugins"))]
198 {
199 let _ = (hook_name, args);
200 }
201 }
202
203 pub fn deliver_response(&self, response: super::api::PluginResponse) {
205 #[cfg(feature = "plugins")]
206 {
207 if let Some(ref manager) = self.inner {
208 manager.deliver_response(response);
209 }
210 }
211 #[cfg(not(feature = "plugins"))]
212 {
213 let _ = response;
214 }
215 }
216
217 pub fn process_commands(&mut self) -> Vec<super::api::PluginCommand> {
219 #[cfg(feature = "plugins")]
220 {
221 if let Some(ref mut manager) = self.inner {
222 return manager.process_commands();
223 }
224 Vec::new()
225 }
226 #[cfg(not(feature = "plugins"))]
227 {
228 Vec::new()
229 }
230 }
231
232 #[cfg(feature = "plugins")]
234 pub fn state_snapshot_handle(&self) -> Option<Arc<RwLock<super::api::EditorStateSnapshot>>> {
235 self.inner.as_ref().map(|m| m.state_snapshot_handle())
236 }
237
238 #[cfg(feature = "plugins")]
240 pub fn execute_action_async(
241 &self,
242 action_name: &str,
243 ) -> Option<anyhow::Result<fresh_plugin_runtime::thread::oneshot::Receiver<anyhow::Result<()>>>>
244 {
245 self.inner
246 .as_ref()
247 .map(|m| m.execute_action_async(action_name))
248 }
249
250 #[cfg(feature = "plugins")]
252 pub fn list_plugins(
253 &self,
254 ) -> Vec<fresh_plugin_runtime::backend::quickjs_backend::TsPluginInfo> {
255 self.inner
256 .as_ref()
257 .map(|m| m.list_plugins())
258 .unwrap_or_default()
259 }
260
261 #[cfg(feature = "plugins")]
263 pub fn reload_plugin(&self, name: &str) -> anyhow::Result<()> {
264 self.inner
265 .as_ref()
266 .ok_or_else(|| anyhow::anyhow!("Plugin system not active"))?
267 .reload_plugin(name)
268 }
269
270 pub fn has_hook_handlers(&self, hook_name: &str) -> bool {
272 #[cfg(feature = "plugins")]
273 {
274 self.inner
275 .as_ref()
276 .map(|m| m.has_hook_handlers(hook_name))
277 .unwrap_or(false)
278 }
279 #[cfg(not(feature = "plugins"))]
280 {
281 let _ = hook_name;
282 false
283 }
284 }
285
286 #[cfg(feature = "plugins")]
288 pub fn resolve_callback(&self, callback_id: super::api::JsCallbackId, result_json: String) {
289 if let Some(inner) = &self.inner {
290 inner.resolve_callback(callback_id, result_json);
291 }
292 }
293
294 #[cfg(not(feature = "plugins"))]
296 pub fn resolve_callback(
297 &self,
298 callback_id: fresh_core::api::JsCallbackId,
299 result_json: String,
300 ) {
301 let _ = (callback_id, result_json);
302 }
303
304 #[cfg(feature = "plugins")]
306 pub fn reject_callback(&self, callback_id: super::api::JsCallbackId, error: String) {
307 if let Some(inner) = &self.inner {
308 inner.reject_callback(callback_id, error);
309 }
310 }
311
312 #[cfg(not(feature = "plugins"))]
314 pub fn reject_callback(&self, callback_id: fresh_core::api::JsCallbackId, error: String) {
315 let _ = (callback_id, error);
316 }
317}