use crate::config_io::DirectoryContext;
use crate::input::command_registry::CommandRegistry;
use fresh_core::config::PluginConfig;
use std::collections::HashMap;
use std::path::Path;
use std::sync::{Arc, RwLock};
#[cfg(feature = "plugins")]
use super::bridge::EditorServiceBridge;
#[cfg(feature = "plugins")]
use fresh_plugin_runtime::PluginThreadHandle;
pub struct PluginManager {
#[cfg(feature = "plugins")]
inner: Option<PluginThreadHandle>,
#[cfg(not(feature = "plugins"))]
_phantom: std::marker::PhantomData<()>,
}
impl PluginManager {
pub fn new(
enable: bool,
command_registry: Arc<RwLock<CommandRegistry>>,
dir_context: DirectoryContext,
) -> Self {
#[cfg(feature = "plugins")]
{
if enable {
let services = Arc::new(EditorServiceBridge {
command_registry: command_registry.clone(),
dir_context,
});
match PluginThreadHandle::spawn(services) {
Ok(handle) => {
return Self {
inner: Some(handle),
}
}
Err(e) => {
tracing::error!("Failed to spawn TypeScript plugin thread: {}", e);
#[cfg(debug_assertions)]
panic!("TypeScript plugin thread creation failed: {}", e);
}
}
} else {
tracing::info!("Plugins disabled via --no-plugins flag");
}
Self { inner: None }
}
#[cfg(not(feature = "plugins"))]
{
let _ = command_registry; let _ = dir_context; if enable {
tracing::warn!("Plugins requested but compiled without plugin support");
}
Self {
_phantom: std::marker::PhantomData,
}
}
}
pub fn is_active(&self) -> bool {
#[cfg(feature = "plugins")]
{
self.inner.is_some()
}
#[cfg(not(feature = "plugins"))]
{
false
}
}
pub fn is_alive(&self) -> bool {
#[cfg(feature = "plugins")]
{
self.inner.as_ref().map(|h| h.is_alive()).unwrap_or(false)
}
#[cfg(not(feature = "plugins"))]
{
false
}
}
pub fn check_thread_health(&mut self) {
#[cfg(feature = "plugins")]
{
if let Some(ref mut handle) = self.inner {
handle.check_thread_health();
}
}
}
pub fn load_plugins_from_dir(&self, dir: &Path) -> Vec<String> {
#[cfg(feature = "plugins")]
{
if let Some(ref manager) = self.inner {
return manager.load_plugins_from_dir(dir);
}
Vec::new()
}
#[cfg(not(feature = "plugins"))]
{
let _ = dir;
Vec::new()
}
}
#[cfg(feature = "plugins")]
pub fn load_plugins_from_dir_with_config(
&self,
dir: &Path,
plugin_configs: &HashMap<String, PluginConfig>,
) -> (Vec<String>, HashMap<String, PluginConfig>) {
if let Some(ref manager) = self.inner {
return manager.load_plugins_from_dir_with_config(dir, plugin_configs);
}
(Vec::new(), HashMap::new())
}
#[cfg(not(feature = "plugins"))]
pub fn load_plugins_from_dir_with_config(
&self,
dir: &Path,
plugin_configs: &HashMap<String, PluginConfig>,
) -> (Vec<String>, HashMap<String, PluginConfig>) {
let _ = (dir, plugin_configs);
(Vec::new(), HashMap::new())
}
pub fn unload_plugin(&self, name: &str) -> anyhow::Result<()> {
#[cfg(feature = "plugins")]
{
self.inner
.as_ref()
.ok_or_else(|| anyhow::anyhow!("Plugin system not active"))?
.unload_plugin(name)
}
#[cfg(not(feature = "plugins"))]
{
let _ = name;
Ok(())
}
}
pub fn load_plugin(&self, path: &Path) -> anyhow::Result<()> {
#[cfg(feature = "plugins")]
{
self.inner
.as_ref()
.ok_or_else(|| anyhow::anyhow!("Plugin system not active"))?
.load_plugin(path)
}
#[cfg(not(feature = "plugins"))]
{
let _ = path;
Ok(())
}
}
pub fn run_hook(&self, hook_name: &str, args: super::hooks::HookArgs) {
#[cfg(feature = "plugins")]
{
if let Some(ref manager) = self.inner {
manager.run_hook(hook_name, args);
}
}
#[cfg(not(feature = "plugins"))]
{
let _ = (hook_name, args);
}
}
pub fn deliver_response(&self, response: super::api::PluginResponse) {
#[cfg(feature = "plugins")]
{
if let Some(ref manager) = self.inner {
manager.deliver_response(response);
}
}
#[cfg(not(feature = "plugins"))]
{
let _ = response;
}
}
pub fn process_commands(&mut self) -> Vec<super::api::PluginCommand> {
#[cfg(feature = "plugins")]
{
if let Some(ref mut manager) = self.inner {
return manager.process_commands();
}
Vec::new()
}
#[cfg(not(feature = "plugins"))]
{
Vec::new()
}
}
#[cfg(feature = "plugins")]
pub fn state_snapshot_handle(&self) -> Option<Arc<RwLock<super::api::EditorStateSnapshot>>> {
self.inner.as_ref().map(|m| m.state_snapshot_handle())
}
#[cfg(feature = "plugins")]
pub fn execute_action_async(
&self,
action_name: &str,
) -> Option<anyhow::Result<fresh_plugin_runtime::thread::oneshot::Receiver<anyhow::Result<()>>>>
{
self.inner
.as_ref()
.map(|m| m.execute_action_async(action_name))
}
#[cfg(feature = "plugins")]
pub fn list_plugins(
&self,
) -> Vec<fresh_plugin_runtime::backend::quickjs_backend::TsPluginInfo> {
self.inner
.as_ref()
.map(|m| m.list_plugins())
.unwrap_or_default()
}
#[cfg(feature = "plugins")]
pub fn reload_plugin(&self, name: &str) -> anyhow::Result<()> {
self.inner
.as_ref()
.ok_or_else(|| anyhow::anyhow!("Plugin system not active"))?
.reload_plugin(name)
}
pub fn has_hook_handlers(&self, hook_name: &str) -> bool {
#[cfg(feature = "plugins")]
{
self.inner
.as_ref()
.map(|m| m.has_hook_handlers(hook_name))
.unwrap_or(false)
}
#[cfg(not(feature = "plugins"))]
{
let _ = hook_name;
false
}
}
#[cfg(feature = "plugins")]
pub fn resolve_callback(&self, callback_id: super::api::JsCallbackId, result_json: String) {
if let Some(inner) = &self.inner {
inner.resolve_callback(callback_id, result_json);
}
}
#[cfg(not(feature = "plugins"))]
pub fn resolve_callback(
&self,
callback_id: fresh_core::api::JsCallbackId,
result_json: String,
) {
let _ = (callback_id, result_json);
}
#[cfg(feature = "plugins")]
pub fn reject_callback(&self, callback_id: super::api::JsCallbackId, error: String) {
if let Some(inner) = &self.inner {
inner.reject_callback(callback_id, error);
}
}
#[cfg(not(feature = "plugins"))]
pub fn reject_callback(&self, callback_id: fresh_core::api::JsCallbackId, error: String) {
let _ = (callback_id, error);
}
}