Skip to main content

santui_core/
plugin.rs

1use crate::auth::{AuthHandle, User};
2use crate::theme::Theme;
3use crossterm::event::{KeyEvent, MouseEvent};
4use ratatui::layout::Rect;
5use ratatui::Frame;
6use std::path::{Path, PathBuf};
7use std::sync::Arc;
8
9/// Factory that creates a `Box<dyn Plugin>` from an id, name, and binary path.
10/// The binary (`santui`) sets this to `IpcPluginHost::new_boxed`.
11pub type PluginFactory = Arc<dyn Fn(&str, &str, &Path) -> Box<dyn Plugin + Send> + Send + Sync>;
12
13/// A command that a plugin registers for the Ctrl+P palette.
14#[derive(Debug, Clone)]
15pub struct PluginCmdItem {
16    pub category: String,
17    pub label: String,
18}
19
20pub trait Plugin: Send {
21    fn id(&self) -> &str;
22    fn name(&self) -> &str;
23    fn init(&mut self, ctx: &mut PluginContext) -> Result<(), Box<dyn std::error::Error>>;
24    fn handle_key(&mut self, _key: KeyEvent) -> bool {
25        false
26    }
27    fn handle_mouse(&mut self, _event: &MouseEvent) -> bool {
28        false
29    }
30    fn render(&self, _f: &mut Frame, _area: Rect) {}
31    fn tick(&mut self) {}
32    fn on_focus(&mut self) {}
33    fn on_blur(&mut self) {}
34    fn on_theme_change(&mut self, _theme: &Theme) {}
35    fn on_user_update(&mut self, _user: Option<&User>) {}
36    fn status_hints(&self) -> Vec<(String, String)> {
37        vec![]
38    }
39    /// Palette commands that this plugin registers (category, label).
40    fn commands(&self) -> Vec<PluginCmdItem> {
41        vec![]
42    }
43    /// Called when a palette command from this plugin is selected.
44    /// `index` is the position in `commands()`.
45    fn handle_palette_command(&mut self, _index: usize) {}
46
47    /// Called when a plugin-to-plugin message arrives.
48    fn on_plugin_message(&mut self, _from: &str, _action: &str, _data: &str) {}
49
50    /// Called before the plugin is unloaded (hot-reload or app exit).
51    /// The plugin should flush state, close handles, and prepare to be dropped.
52    /// For IPC plugins this sends `Shutdown` and waits briefly for a response
53    /// before the child process is killed.
54    fn shutdown(&mut self) {}
55
56    /// Return the filesystem path to this plugin's binary, if it runs as an
57    /// external process.  `None` for in-process plugins.  Used by the hot-reload
58    /// mechanism to detect when the binary has been updated on disk.
59    fn binary_path(&self) -> Option<&Path> {
60        None
61    }
62
63    /// Whether the plugin process is still running.
64    /// Returns `true` for in-process plugins.
65    /// IPC plugins override this to check the child process.
66    fn is_alive(&self) -> bool {
67        true
68    }
69
70    /// Whether the plugin supports running in the background when the user
71    /// presses Esc (e.g., audio players that should keep playing).
72    /// Default is `false`. Override to return `true` for background-capable plugins.
73    fn can_background(&self) -> bool {
74        false
75    }
76
77    /// Set runtime capabilities (e.g. `"background"`) declared in the plugin manifest.
78    /// Default is no-op; IPC plugins override this to store the value.
79    fn set_capabilities(&mut self, _caps: Vec<String>) {}
80
81    /// Whether this plugin should stay loaded when the user presses Esc.
82    /// Persistent plugins are blurred but not shut down, keeping their
83    /// palette entries and background processes alive.
84    /// Default is `false`. Override to return `true` for plugins that
85    /// must not be unloaded (e.g., the registry plugin).
86    fn persistent(&self) -> bool {
87        false
88    }
89
90    /// Mark this plugin as persistent.  Default is no-op; IPC plugins
91    /// override this to store the value.
92    fn set_persistent(&mut self, _persistent: bool) {}
93
94    /// Check if a pending Esc key response has been resolved.
95    /// Returns `Some(consumed)` if resolved, `None` if still pending
96    /// or no Esc was sent.
97    /// IPC plugins override this; in-process plugins don't need it.
98    fn take_pending_esc_result(&mut self) -> Option<bool> {
99        None
100    }
101}
102
103pub struct PluginContext {
104    pub theme: Theme,
105    pub auth: Option<Arc<dyn AuthHandle>>,
106    /// Santui data directory (e.g. `~/.santui`). Plugins can use this
107    /// for persistent storage. The registry plugin uses it to find
108    /// installed plugins and `registry.toml`.
109    pub data_dir: PathBuf,
110}
111
112impl PluginContext {
113    pub fn new() -> Self {
114        PluginContext {
115            theme: Theme::default(),
116            auth: None,
117            data_dir: PathBuf::new(),
118        }
119    }
120}
121
122impl Default for PluginContext {
123    fn default() -> Self {
124        Self::new()
125    }
126}