Skip to main content

enya_plugin/
traits.rs

1//! Core plugin traits and types.
2
3use std::any::Any;
4
5use bitflags::bitflags;
6
7use crate::hooks::{CommandHook, KeyboardHook, LifecycleHook, PaneHook, ThemeHook};
8use crate::theme::ThemeDefinition;
9use crate::types::{
10    CustomChartConfig, CustomTableConfig, GaugePaneConfig, PluginContext, StatPaneConfig, Theme,
11};
12
13/// Result type for plugin operations.
14pub type PluginResult<T> = Result<T, PluginError>;
15
16/// Errors that can occur during plugin operations.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub enum PluginError {
19    /// Plugin initialization failed
20    InitializationFailed(String),
21    /// Plugin is not compatible with the current editor version
22    IncompatibleVersion { required: String, actual: String },
23    /// A required capability is not available
24    MissingCapability(String),
25    /// Plugin configuration is invalid
26    InvalidConfiguration(String),
27    /// Plugin dependency is missing
28    MissingDependency(String),
29    /// Plugin is already registered
30    AlreadyRegistered(String),
31    /// Plugin not found
32    NotFound(String),
33    /// Plugin operation failed
34    OperationFailed(String),
35}
36
37impl std::fmt::Display for PluginError {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        match self {
40            Self::InitializationFailed(msg) => write!(f, "Plugin initialization failed: {msg}"),
41            Self::IncompatibleVersion { required, actual } => {
42                write!(f, "Incompatible version: requires {required}, got {actual}")
43            }
44            Self::MissingCapability(cap) => write!(f, "Missing capability: {cap}"),
45            Self::InvalidConfiguration(msg) => write!(f, "Invalid configuration: {msg}"),
46            Self::MissingDependency(dep) => write!(f, "Missing dependency: {dep}"),
47            Self::AlreadyRegistered(name) => write!(f, "Plugin already registered: {name}"),
48            Self::NotFound(name) => write!(f, "Plugin not found: {name}"),
49            Self::OperationFailed(msg) => write!(f, "Operation failed: {msg}"),
50        }
51    }
52}
53
54impl std::error::Error for PluginError {}
55
56bitflags! {
57    /// Capabilities that a plugin can provide.
58    ///
59    /// Plugins declare their capabilities to enable feature-based loading
60    /// and to provide hints for the plugin registry.
61    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
62    pub struct PluginCapabilities: u32 {
63        /// Plugin provides custom commands for the command palette
64        const COMMANDS = 1 << 0;
65        /// Plugin provides custom pane types
66        const PANES = 1 << 1;
67        /// Plugin provides custom keyboard shortcuts
68        const KEYBOARD = 1 << 2;
69        /// Plugin hooks into theme changes
70        const THEMING = 1 << 3;
71        /// Plugin provides custom visualizations
72        const VISUALIZATIONS = 1 << 4;
73        /// Plugin provides data source integrations
74        const DATA_SOURCES = 1 << 5;
75        /// Plugin provides agent commands (AI integration)
76        const AGENT_COMMANDS = 1 << 6;
77        /// Plugin provides status line segments
78        const STATUS_LINE = 1 << 7;
79        /// Plugin provides finder sources (for unified finder)
80        const FINDER_SOURCES = 1 << 8;
81        /// Plugin provides custom color themes
82        const CUSTOM_THEMES = 1 << 9;
83    }
84}
85
86/// Configuration for a custom command provided by a plugin.
87#[derive(Debug, Clone)]
88pub struct CommandConfig {
89    /// Command name (e.g., "my-command")
90    pub name: String,
91    /// Aliases for the command (e.g., ["mc", "mycmd"])
92    pub aliases: Vec<String>,
93    /// Human-readable description
94    pub description: String,
95    /// Whether the command accepts arguments
96    pub accepts_args: bool,
97}
98
99/// Configuration for a custom pane type provided by a plugin.
100#[derive(Debug, Clone)]
101pub struct PaneConfig {
102    /// Pane type name (e.g., "custom-chart")
103    pub name: String,
104    /// Human-readable label for the pane type
105    pub label: String,
106    /// Description shown in pane creation UI
107    pub description: String,
108    /// Icon identifier (optional)
109    pub icon: Option<String>,
110}
111
112/// Configuration for a keyboard shortcut provided by a plugin.
113#[derive(Debug, Clone)]
114pub struct KeybindingConfig {
115    /// Key sequence (e.g., "Space+p+c" for leader+p+c)
116    pub keys: String,
117    /// Command to execute when triggered
118    pub command: String,
119    /// Description shown in which-key overlay
120    pub description: String,
121    /// Modes where this binding is active (empty = all modes)
122    pub modes: Vec<String>,
123}
124
125/// The core plugin trait that all plugins must implement.
126///
127/// This trait defines the interface for plugin lifecycle management,
128/// capability declaration, and hook registration.
129pub trait Plugin: Any + Send + Sync {
130    /// Returns the unique name of the plugin.
131    ///
132    /// This name is used for identification and should be kebab-case
133    /// (e.g., "git-blame", "custom-charts").
134    fn name(&self) -> &'static str;
135
136    /// Returns the version of the plugin (semver format).
137    fn version(&self) -> &'static str;
138
139    /// Returns a human-readable description of the plugin.
140    fn description(&self) -> &'static str {
141        ""
142    }
143
144    /// Returns the capabilities this plugin provides.
145    fn capabilities(&self) -> PluginCapabilities;
146
147    /// Returns the minimum editor version required.
148    fn min_editor_version(&self) -> Option<&'static str> {
149        None
150    }
151
152    /// Returns names of plugins this plugin depends on.
153    fn dependencies(&self) -> &[&'static str] {
154        &[]
155    }
156
157    /// Initialize the plugin with the given context.
158    ///
159    /// This is called once when the plugin is first loaded.
160    /// Return an error to prevent the plugin from loading.
161    fn init(&mut self, ctx: &PluginContext) -> PluginResult<()>;
162
163    /// Activate the plugin.
164    ///
165    /// This is called when the plugin is enabled (either at startup
166    /// if enabled by default, or when the user enables it).
167    fn activate(&mut self, _ctx: &PluginContext) -> PluginResult<()> {
168        Ok(())
169    }
170
171    /// Deactivate the plugin.
172    ///
173    /// This is called when the plugin is disabled. Plugins should
174    /// clean up any resources and remove any UI elements.
175    fn deactivate(&mut self, _ctx: &PluginContext) -> PluginResult<()> {
176        Ok(())
177    }
178
179    /// Returns the commands this plugin provides.
180    fn commands(&self) -> Vec<CommandConfig> {
181        vec![]
182    }
183
184    /// Returns the pane types this plugin provides.
185    fn pane_types(&self) -> Vec<PaneConfig> {
186        vec![]
187    }
188
189    /// Returns the keybindings this plugin provides.
190    fn keybindings(&self) -> Vec<KeybindingConfig> {
191        vec![]
192    }
193
194    /// Returns the custom themes this plugin provides.
195    ///
196    /// Only called if the plugin declares `CUSTOM_THEMES` capability.
197    fn themes(&self) -> Vec<ThemeDefinition> {
198        vec![]
199    }
200
201    /// Returns the custom table pane types this plugin provides.
202    ///
203    /// Only called if the plugin declares `PANES` capability.
204    fn custom_table_panes(&self) -> Vec<CustomTableConfig> {
205        vec![]
206    }
207
208    /// Returns the custom chart pane types this plugin provides.
209    ///
210    /// Only called if the plugin declares `PANES` capability.
211    fn custom_chart_panes(&self) -> Vec<CustomChartConfig> {
212        vec![]
213    }
214
215    /// Returns the custom stat pane types this plugin provides.
216    ///
217    /// Only called if the plugin declares `PANES` capability.
218    fn custom_stat_panes(&self) -> Vec<StatPaneConfig> {
219        vec![]
220    }
221
222    /// Returns the custom gauge pane types this plugin provides.
223    ///
224    /// Only called if the plugin declares `PANES` capability.
225    fn custom_gauge_panes(&self) -> Vec<GaugePaneConfig> {
226        vec![]
227    }
228
229    /// Returns the lifecycle hooks this plugin wants to receive.
230    fn lifecycle_hooks(&self) -> Option<Box<dyn LifecycleHook>> {
231        None
232    }
233
234    /// Returns the command hooks this plugin wants to receive.
235    fn command_hooks(&self) -> Option<Box<dyn CommandHook>> {
236        None
237    }
238
239    /// Returns the keyboard hooks this plugin wants to receive.
240    fn keyboard_hooks(&self) -> Option<Box<dyn KeyboardHook>> {
241        None
242    }
243
244    /// Returns the theme hooks this plugin wants to receive.
245    fn theme_hooks(&self) -> Option<Box<dyn ThemeHook>> {
246        None
247    }
248
249    /// Returns the pane hooks this plugin wants to receive.
250    fn pane_hooks(&self) -> Option<Box<dyn PaneHook>> {
251        None
252    }
253
254    /// Execute a command provided by this plugin.
255    ///
256    /// Only called if the plugin declares `COMMANDS` capability.
257    fn execute_command(&mut self, _command: &str, _args: &str, _ctx: &PluginContext) -> bool {
258        false
259    }
260
261    /// Called when the theme changes.
262    ///
263    /// Only called if the plugin declares `THEMING` capability.
264    fn on_theme_changed(&mut self, _theme: Theme) {}
265
266    /// Get pane types that have auto-refresh enabled.
267    ///
268    /// Returns a list of (pane_type_name, refresh_interval_secs) pairs.
269    /// Only called if the plugin declares `PANES` capability.
270    fn refreshable_pane_types(&self) -> Vec<(&str, u32)> {
271        vec![]
272    }
273
274    /// Trigger a refresh for a specific pane type.
275    ///
276    /// This is called by the editor when a pane needs to be refreshed
277    /// (based on its refresh_interval). The plugin should fetch new data
278    /// and call the appropriate `set_*_data` function.
279    ///
280    /// Returns true if the refresh was triggered successfully.
281    fn trigger_pane_refresh(&mut self, _pane_type: &str, _ctx: &PluginContext) -> bool {
282        false
283    }
284
285    /// Get a reference to self as Any (for downcasting).
286    fn as_any(&self) -> &dyn Any;
287
288    /// Get a mutable reference to self as Any (for downcasting).
289    fn as_any_mut(&mut self) -> &mut dyn Any;
290}