pub mod events;
pub mod manifest;
pub mod permissions;
pub mod registry;
use std::fmt;
use std::path::PathBuf;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use thiserror::Error;
use crate::link_parser::LinkFormat;
pub use events::*;
pub use manifest::*;
pub use registry::PluginRegistry;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[cfg_attr(feature = "typescript", ts(export, export_to = "bindings/"))]
pub struct PluginId(pub String);
impl fmt::Display for PluginId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl From<&str> for PluginId {
fn from(s: &str) -> Self {
PluginId(s.to_string())
}
}
#[derive(Debug, Error)]
pub enum PluginError {
#[error("Plugin init failed: {0}")]
InitFailed(String),
#[error("Plugin command error: {0}")]
CommandError(String),
#[error("Permission denied: {0}")]
PermissionDenied(String),
#[error("Plugin call timed out: {0}")]
Timeout(String),
#[error("Plugin not found: {0}")]
PluginNotFound(String),
#[error("Protocol version mismatch: {0}")]
ProtocolMismatch(String),
#[error("Plugin config error: {0}")]
ConfigError(String),
#[error("{0}")]
Other(String),
}
impl PluginError {
pub fn is_retryable(&self) -> bool {
matches!(self, PluginError::Timeout(_))
}
pub fn is_permission_error(&self) -> bool {
matches!(self, PluginError::PermissionDenied(_))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[cfg_attr(feature = "typescript", ts(export, export_to = "bindings/"))]
pub enum PluginHealth {
Healthy,
Degraded(String),
Failed(String),
}
#[derive(Default)]
pub struct PluginContext {
pub workspace_root: Option<PathBuf>,
pub link_format: LinkFormat,
}
impl PluginContext {
pub fn new(workspace_root: Option<PathBuf>, link_format: LinkFormat) -> Self {
Self {
workspace_root,
link_format,
}
}
}
#[cfg(not(target_arch = "wasm32"))]
#[async_trait]
pub trait Plugin: Send + Sync + 'static {
fn id(&self) -> PluginId;
fn manifest(&self) -> PluginManifest;
async fn init(&self, ctx: &PluginContext) -> Result<(), PluginError> {
let _ = ctx;
Ok(())
}
async fn shutdown(&self) -> Result<(), PluginError> {
Ok(())
}
}
#[cfg(target_arch = "wasm32")]
#[async_trait(?Send)]
pub trait Plugin: 'static {
fn id(&self) -> PluginId;
fn manifest(&self) -> PluginManifest;
async fn init(&self, ctx: &PluginContext) -> Result<(), PluginError> {
let _ = ctx;
Ok(())
}
async fn shutdown(&self) -> Result<(), PluginError> {
Ok(())
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait WorkspacePlugin: Plugin {
async fn on_workspace_opened(&self, event: &WorkspaceOpenedEvent) {
let _ = event;
}
async fn on_workspace_closed(&self, event: &WorkspaceClosedEvent) {
let _ = event;
}
async fn on_workspace_changed(&self, event: &WorkspaceChangedEvent) {
let _ = event;
}
async fn on_workspace_committed(&self, event: &WorkspaceCommittedEvent) {
let _ = event;
}
async fn handle_command(
&self,
cmd: &str,
params: JsonValue,
) -> Option<Result<JsonValue, PluginError>> {
let _ = (cmd, params);
None
}
async fn notify_workspace_modified(&self) {}
async fn on_body_doc_renamed(&self, _old_path: &str, _new_path: &str) {}
async fn on_body_doc_deleted(&self, _path: &str) {}
async fn track_file_for_sync(&self, _canonical_path: &str) {}
fn track_content_for_sync(&self, _canonical_path: &str, _content: &str) {}
fn get_canonical_path(&self, _storage_path: &str) -> Option<String> {
None
}
fn get_file_title(&self, _canonical_path: &str) -> Option<String> {
None
}
async fn get_config(&self) -> Option<serde_json::Value> {
None
}
async fn set_config(&self, _config: serde_json::Value) -> std::result::Result<(), PluginError> {
Ok(())
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait FilePlugin: Plugin {
async fn on_file_saved(&self, event: &FileSavedEvent) {
let _ = event;
}
async fn on_file_created(&self, event: &FileCreatedEvent) {
let _ = event;
}
async fn on_file_deleted(&self, event: &FileDeletedEvent) {
let _ = event;
}
async fn on_file_moved(&self, event: &FileMovedEvent) {
let _ = event;
}
}