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}