Skip to main content

scarab_plugin_api/
types.rs

1//! Common types used throughout the plugin API
2
3pub use scarab_protocol::{ModalItem, OverlayStyle};
4use serde::{Deserialize, Serialize};
5
6/// Configuration for spawning an overlay
7#[derive(Debug, Clone, PartialEq)]
8pub struct OverlayConfig {
9    /// X position (column) for the overlay
10    pub x: u16,
11    /// Y position (row) for the overlay
12    pub y: u16,
13    /// Content to display in the overlay
14    pub content: String,
15    /// Visual style for the overlay
16    pub style: OverlayStyle,
17}
18
19impl OverlayConfig {
20    /// Create a new overlay config
21    pub fn new(x: u16, y: u16, content: impl Into<String>) -> Self {
22        Self {
23            x,
24            y,
25            content: content.into(),
26            style: OverlayStyle::default(),
27        }
28    }
29
30    /// Set the style for this overlay
31    pub fn with_style(mut self, style: OverlayStyle) -> Self {
32        self.style = style;
33        self
34    }
35}
36
37/// Configuration for a status bar item
38#[derive(Debug, Clone, PartialEq)]
39pub struct StatusBarItem {
40    /// Label/identifier for this item
41    pub label: String,
42    /// Content to display
43    pub content: String,
44    /// Priority (higher = further right)
45    pub priority: i32,
46}
47
48impl StatusBarItem {
49    /// Create a new status bar item
50    pub fn new(label: impl Into<String>, content: impl Into<String>) -> Self {
51        Self {
52            label: label.into(),
53            content: content.into(),
54            priority: 0,
55        }
56    }
57
58    /// Set the priority for this status bar item
59    pub fn with_priority(mut self, priority: i32) -> Self {
60        self.priority = priority;
61        self
62    }
63}
64
65/// Direction for prompt jump navigation
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum JumpDirection {
68    /// Jump to previous prompt
69    Up,
70    /// Jump to next prompt
71    Down,
72    /// Jump to first prompt in buffer
73    First,
74    /// Jump to last prompt in buffer
75    Last,
76}
77
78/// Command sent from plugin to daemon/client
79#[derive(Debug, Clone)]
80pub enum RemoteCommand {
81    DrawOverlay {
82        id: u64,
83        x: u16,
84        y: u16,
85        text: String,
86        style: OverlayStyle,
87    },
88    ClearOverlays {
89        id: Option<u64>,
90    },
91    ShowModal {
92        title: String,
93        items: Vec<ModalItem>,
94    },
95    PluginLog {
96        plugin_name: String,
97        level: crate::context::LogLevel,
98        message: String,
99    },
100    PluginNotify {
101        title: String,
102        body: String,
103        level: crate::context::NotifyLevel,
104    },
105    ThemeUpdate {
106        theme_json: String,
107    },
108    // Navigation commands (ECS-safe host bindings)
109    NavEnterHintMode {
110        plugin_name: String,
111    },
112    NavExitMode {
113        plugin_name: String,
114    },
115    NavRegisterFocusable {
116        plugin_name: String,
117        x: u16,
118        y: u16,
119        width: u16,
120        height: u16,
121        label: String,
122        action: scarab_protocol::NavFocusableAction,
123    },
124    NavUnregisterFocusable {
125        plugin_name: String,
126        focusable_id: u64,
127    },
128    /// Spawn an overlay at a given position
129    SpawnOverlay {
130        plugin_name: String,
131        overlay_id: u64,
132        config: OverlayConfig,
133    },
134    /// Remove a previously spawned overlay
135    RemoveOverlay {
136        plugin_name: String,
137        overlay_id: u64,
138    },
139    /// Add a status bar item
140    AddStatusItem {
141        plugin_name: String,
142        item_id: u64,
143        item: StatusBarItem,
144    },
145    /// Remove a status bar item
146    RemoveStatusItem {
147        plugin_name: String,
148        item_id: u64,
149    },
150    /// Trigger prompt jump navigation
151    PromptJump {
152        plugin_name: String,
153        direction: JumpDirection,
154    },
155    /// Apply a named theme
156    ApplyTheme {
157        plugin_name: String,
158        theme_name: String,
159    },
160    /// Set a specific palette color
161    SetPaletteColor {
162        plugin_name: String,
163        color_name: String,
164        value: String,
165    },
166    /// Get the current theme name (async response via callback)
167    GetCurrentTheme {
168        plugin_name: String,
169    },
170}
171
172/// Action that a plugin hook can return
173#[derive(Debug, Clone, PartialEq, Eq)]
174pub enum Action {
175    /// Continue processing with next plugin
176    Continue,
177    /// Stop processing, don't call remaining plugins
178    Stop,
179    /// Modify the data and continue
180    Modify(Vec<u8>),
181}
182
183impl Action {
184    /// Check if this action modifies data
185    pub fn is_modify(&self) -> bool {
186        matches!(self, Action::Modify(_))
187    }
188
189    /// Check if this action stops processing
190    pub fn is_stop(&self) -> bool {
191        matches!(self, Action::Stop)
192    }
193}
194
195/// Type of hook being executed
196#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
197pub enum HookType {
198    /// Before output is displayed
199    PreOutput,
200    /// After input is received
201    PostInput,
202    /// Before command is executed
203    PreCommand,
204    /// After command completes
205    PostCommand,
206    /// Terminal resize event
207    OnResize,
208    /// Client attached
209    OnAttach,
210    /// Client detached
211    OnDetach,
212}
213
214impl HookType {
215    /// Get all hook types
216    pub fn all() -> &'static [HookType] {
217        &[
218            HookType::PreOutput,
219            HookType::PostInput,
220            HookType::PreCommand,
221            HookType::PostCommand,
222            HookType::OnResize,
223            HookType::OnAttach,
224            HookType::OnDetach,
225        ]
226    }
227
228    /// Get human-readable name
229    pub fn name(&self) -> &'static str {
230        match self {
231            HookType::PreOutput => "pre-output",
232            HookType::PostInput => "post-input",
233            HookType::PreCommand => "pre-command",
234            HookType::PostCommand => "post-command",
235            HookType::OnResize => "on-resize",
236            HookType::OnAttach => "on-attach",
237            HookType::OnDetach => "on-detach",
238        }
239    }
240}
241
242/// Information about a loaded plugin with personality
243#[derive(Debug, Clone, Serialize, Deserialize)]
244pub struct PluginInfo {
245    /// Plugin name
246    pub name: String,
247    /// Plugin version
248    pub version: String,
249    /// Plugin description
250    pub description: String,
251    /// Plugin author
252    pub author: String,
253    /// Plugin homepage URL
254    pub homepage: Option<String>,
255    /// API version required
256    pub api_version: String,
257    /// Minimum Scarab version
258    pub min_scarab_version: String,
259    /// Whether plugin is currently enabled
260    pub enabled: bool,
261    /// Number of failures
262    pub failure_count: u32,
263    /// Plugin emoji (for display)
264    #[serde(default)]
265    pub emoji: Option<String>,
266    /// Plugin color (hex code)
267    #[serde(default)]
268    pub color: Option<String>,
269    /// Plugin catchphrase
270    #[serde(default)]
271    pub catchphrase: Option<String>,
272}
273
274impl PluginInfo {
275    /// Create new plugin info
276    pub fn new(
277        name: impl Into<String>,
278        version: impl Into<String>,
279        description: impl Into<String>,
280        author: impl Into<String>,
281    ) -> Self {
282        Self {
283            name: name.into(),
284            version: version.into(),
285            description: description.into(),
286            author: author.into(),
287            homepage: None,
288            api_version: crate::API_VERSION.to_string(),
289            min_scarab_version: "0.1.0".to_string(),
290            enabled: true,
291            failure_count: 0,
292            emoji: None,
293            color: None,
294            catchphrase: None,
295        }
296    }
297
298    /// Get display name with emoji if available
299    pub fn display_name(&self) -> String {
300        if let Some(emoji) = &self.emoji {
301            format!("{} {}", emoji, self.name)
302        } else {
303            self.name.clone()
304        }
305    }
306
307    /// Get plugin mood based on failure count
308    pub fn mood(&self) -> crate::delight::PluginMood {
309        crate::delight::PluginMood::from_failure_count(self.failure_count, 3, self.enabled)
310    }
311}
312
313/// Terminal cell representation
314#[derive(Debug, Clone, Copy, PartialEq, Eq)]
315pub struct Cell {
316    /// Character content
317    pub c: char,
318    /// Foreground color (RGB)
319    pub fg: (u8, u8, u8),
320    /// Background color (RGB)
321    pub bg: (u8, u8, u8),
322    /// Bold flag
323    pub bold: bool,
324    /// Italic flag
325    pub italic: bool,
326    /// Underline flag
327    pub underline: bool,
328}
329
330impl Default for Cell {
331    fn default() -> Self {
332        Self {
333            c: ' ',
334            fg: (255, 255, 255),
335            bg: (0, 0, 0),
336            bold: false,
337            italic: false,
338            underline: false,
339        }
340    }
341}