Skip to main content

santui_core/
plugin.rs

1use crate::auth::{AuthHandle, User};
2use crate::theme::Theme;
3use crossterm::event::KeyEvent;
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 render(&self, _f: &mut Frame, _area: Rect) {}
28    fn tick(&mut self) {}
29    fn on_focus(&mut self) {}
30    fn on_blur(&mut self) {}
31    fn on_theme_change(&mut self, _theme: &Theme) {}
32    fn on_user_update(&mut self, _user: Option<&User>) {}
33    fn status_hints(&self) -> Vec<(String, String)> {
34        vec![]
35    }
36    /// Palette commands that this plugin registers (category, label).
37    fn commands(&self) -> Vec<PluginCmdItem> {
38        vec![]
39    }
40    /// Called when a palette command from this plugin is selected.
41    /// `index` is the position in `commands()`.
42    fn handle_palette_command(&mut self, _index: usize) {}
43
44    /// Called when a plugin-to-plugin message arrives.
45    fn on_plugin_message(&mut self, _from: &str, _action: &str, _data: &str) {}
46
47    /// Called before the plugin is unloaded (hot-reload or app exit).
48    /// The plugin should flush state, close handles, and prepare to be dropped.
49    /// For IPC plugins this sends `Shutdown` and waits briefly for a response
50    /// before the child process is killed.
51    fn shutdown(&mut self) {}
52
53    /// Return the filesystem path to this plugin's binary, if it runs as an
54    /// external process.  `None` for in-process plugins.  Used by the hot-reload
55    /// mechanism to detect when the binary has been updated on disk.
56    fn binary_path(&self) -> Option<&Path> {
57        None
58    }
59
60    /// Whether the plugin process is still running.
61    /// Returns `true` for in-process plugins.
62    /// IPC plugins override this to check the child process.
63    fn is_alive(&self) -> bool {
64        true
65    }
66
67    /// Whether the plugin supports running in the background when the user
68    /// presses Esc (e.g., audio players that should keep playing).
69    /// Default is `false`. Override to return `true` for background-capable plugins.
70    fn can_background(&self) -> bool {
71        false
72    }
73
74    /// Set runtime capabilities (e.g. `"background"`) declared in the plugin manifest.
75    /// Default is no-op; IPC plugins override this to store the value.
76    fn set_capabilities(&mut self, _caps: Vec<String>) {}
77}
78
79pub struct PluginContext {
80    pub theme: Theme,
81    pub auth: Option<Arc<dyn AuthHandle>>,
82    /// Santui data directory (e.g. `~/.santui`). Plugins can use this
83    /// for persistent storage. The registry plugin uses it to find
84    /// installed plugins and `registry.toml`.
85    pub data_dir: PathBuf,
86}
87
88impl PluginContext {
89    pub fn new() -> Self {
90        PluginContext {
91            theme: Theme::default(),
92            auth: None,
93            data_dir: PathBuf::new(),
94        }
95    }
96}
97
98impl Default for PluginContext {
99    fn default() -> Self {
100        Self::new()
101    }
102}