scarab-plugin-api 0.3.3

Plugin API for Scarab terminal emulator: traits, manifest schema, and host bindings for building Scarab plugins
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
//! Status Bar Rendering API for Scarab terminal emulator
//!
//! This module provides types and utilities for programmable status bars.
//! Similar to WezTerm's status bar API, it allows plugins to dynamically
//! update status bar content with rich styling and formatting.
//!
//! ## Example
//!
//! ```rust
//! use scarab_plugin_api::status_bar::{RenderItem, Color, StatusBarUpdate, StatusBarSide};
//!
//! // Create a styled status bar update
//! let items = vec![
//!     RenderItem::Foreground(Color::Hex("#7aa2f7".to_string())),
//!     RenderItem::Text("~/project".to_string()),
//!     RenderItem::Text(" | ".to_string()),
//!     RenderItem::Bold,
//!     RenderItem::Text("12:34".to_string()),
//!     RenderItem::ResetAttributes,
//! ];
//!
//! let update = StatusBarUpdate {
//!     window_id: 1,
//!     side: StatusBarSide::Right,
//!     items,
//! };
//! ```

use serde::{Deserialize, Serialize};

/// A single rendering element for status bar content
///
/// RenderItems are processed sequentially to build styled text for the status bar.
/// They can represent text content, color changes, text attributes, or layout elements.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "type", content = "data")]
pub enum RenderItem {
    /// Display text content
    ///
    /// Renders the provided string with the current text style.
    ///
    /// # Example
    ///
    /// ```rust
    /// use scarab_plugin_api::status_bar::RenderItem;
    ///
    /// let item = RenderItem::Text("Hello, World!".to_string());
    /// ```
    Text(String),

    /// Display a Nerd Font icon
    ///
    /// Renders an icon from the Nerd Font icon set by name.
    /// The icon will use the current foreground color and attributes.
    ///
    /// # Example
    ///
    /// ```rust
    /// use scarab_plugin_api::status_bar::RenderItem;
    ///
    /// let item = RenderItem::Icon("nf-fa-battery_full".to_string());
    /// ```
    Icon(String),

    /// Set foreground (text) color
    ///
    /// Changes the text color for all subsequent text items until
    /// another color change or reset.
    ///
    /// # Example
    ///
    /// ```rust
    /// use scarab_plugin_api::status_bar::{RenderItem, Color};
    ///
    /// let item = RenderItem::Foreground(Color::Rgb(122, 162, 247));
    /// ```
    Foreground(Color),

    /// Set foreground color using ANSI color
    ///
    /// Alternative to `Foreground` using standard ANSI color names.
    ///
    /// # Example
    ///
    /// ```rust
    /// use scarab_plugin_api::status_bar::{RenderItem, AnsiColor};
    ///
    /// let item = RenderItem::ForegroundAnsi(AnsiColor::BrightBlue);
    /// ```
    ForegroundAnsi(AnsiColor),

    /// Set background color
    ///
    /// Changes the background color for all subsequent text items.
    ///
    /// # Example
    ///
    /// ```rust
    /// use scarab_plugin_api::status_bar::{RenderItem, Color};
    ///
    /// let item = RenderItem::Background(Color::Named("darkgray".to_string()));
    /// ```
    Background(Color),

    /// Set background color using ANSI color
    ///
    /// Alternative to `Background` using standard ANSI color names.
    ///
    /// # Example
    ///
    /// ```rust
    /// use scarab_plugin_api::status_bar::{RenderItem, AnsiColor};
    ///
    /// let item = RenderItem::BackgroundAnsi(AnsiColor::Black);
    /// ```
    BackgroundAnsi(AnsiColor),

    /// Enable bold text attribute
    ///
    /// Makes all subsequent text bold until reset or another weight change.
    ///
    /// # Example
    ///
    /// ```rust
    /// use scarab_plugin_api::status_bar::RenderItem;
    ///
    /// let items = vec![
    ///     RenderItem::Bold,
    ///     RenderItem::Text("Important".to_string()),
    /// ];
    /// ```
    Bold,

    /// Enable italic text attribute
    ///
    /// Makes all subsequent text italic until reset.
    ///
    /// # Example
    ///
    /// ```rust
    /// use scarab_plugin_api::status_bar::RenderItem;
    ///
    /// let items = vec![
    ///     RenderItem::Italic,
    ///     RenderItem::Text("Emphasis".to_string()),
    /// ];
    /// ```
    Italic,

    /// Enable underline with specified style
    ///
    /// Underlines all subsequent text with the given style until reset.
    ///
    /// # Example
    ///
    /// ```rust
    /// use scarab_plugin_api::status_bar::{RenderItem, UnderlineStyle};
    ///
    /// let items = vec![
    ///     RenderItem::Underline(UnderlineStyle::Curly),
    ///     RenderItem::Text("Warning".to_string()),
    /// ];
    /// ```
    Underline(UnderlineStyle),

    /// Enable strikethrough text attribute
    ///
    /// Strikes through all subsequent text until reset.
    ///
    /// # Example
    ///
    /// ```rust
    /// use scarab_plugin_api::status_bar::RenderItem;
    ///
    /// let items = vec![
    ///     RenderItem::Strikethrough,
    ///     RenderItem::Text("Deprecated".to_string()),
    /// ];
    /// ```
    Strikethrough,

    /// Reset all text attributes to defaults
    ///
    /// Clears all colors, bold, italic, underline, and strikethrough attributes.
    ///
    /// # Example
    ///
    /// ```rust
    /// use scarab_plugin_api::status_bar::RenderItem;
    ///
    /// let items = vec![
    ///     RenderItem::Bold,
    ///     RenderItem::Text("Bold".to_string()),
    ///     RenderItem::ResetAttributes,
    ///     RenderItem::Text("Normal".to_string()),
    /// ];
    /// ```
    ResetAttributes,

    /// Reset foreground color to default
    ///
    /// Clears only the foreground color, keeping other attributes.
    ///
    /// # Example
    ///
    /// ```rust
    /// use scarab_plugin_api::status_bar::{RenderItem, Color};
    ///
    /// let items = vec![
    ///     RenderItem::Bold,
    ///     RenderItem::Foreground(Color::Hex("#ff0000".to_string())),
    ///     RenderItem::Text("Red & Bold".to_string()),
    ///     RenderItem::ResetForeground,
    ///     RenderItem::Text("Default color, still bold".to_string()),
    /// ];
    /// ```
    ResetForeground,

    /// Reset background color to default
    ///
    /// Clears only the background color, keeping other attributes.
    ///
    /// # Example
    ///
    /// ```rust
    /// use scarab_plugin_api::status_bar::{RenderItem, Color};
    ///
    /// let items = vec![
    ///     RenderItem::Background(Color::Hex("#333333".to_string())),
    ///     RenderItem::Text("Dark background".to_string()),
    ///     RenderItem::ResetBackground,
    ///     RenderItem::Text("Default background".to_string()),
    /// ];
    /// ```
    ResetBackground,

    /// Insert flexible spacing
    ///
    /// Creates space that expands to fill available room.
    /// Useful for pushing content to opposite ends of the status bar.
    ///
    /// # Example
    ///
    /// ```rust
    /// use scarab_plugin_api::status_bar::RenderItem;
    ///
    /// let items = vec![
    ///     RenderItem::Text("Left".to_string()),
    ///     RenderItem::Spacer,
    ///     RenderItem::Text("Right".to_string()),
    /// ];
    /// ```
    Spacer,

    /// Insert fixed spacing
    ///
    /// Adds a fixed number of space characters (cells).
    ///
    /// # Example
    ///
    /// ```rust
    /// use scarab_plugin_api::status_bar::RenderItem;
    ///
    /// let items = vec![
    ///     RenderItem::Text("A".to_string()),
    ///     RenderItem::Padding(3),
    ///     RenderItem::Text("B".to_string()),
    /// ];
    /// ```
    Padding(u8),

    /// Insert a separator string
    ///
    /// Convenience item for common separators like " | " or " • ".
    ///
    /// # Example
    ///
    /// ```rust
    /// use scarab_plugin_api::status_bar::RenderItem;
    ///
    /// let items = vec![
    ///     RenderItem::Text("Section 1".to_string()),
    ///     RenderItem::Separator(" | ".to_string()),
    ///     RenderItem::Text("Section 2".to_string()),
    /// ];
    /// ```
    Separator(String),
}

/// Color specification for text rendering
///
/// Supports RGB values, hex notation, and named colors.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "type", content = "value")]
pub enum Color {
    /// RGB color with 8-bit components
    ///
    /// # Example
    ///
    /// ```rust
    /// use scarab_plugin_api::status_bar::Color;
    ///
    /// let color = Color::Rgb(122, 162, 247);  // Light blue
    /// ```
    Rgb(u8, u8, u8),

    /// Hexadecimal color string
    ///
    /// Supports standard hex color notation with or without '#' prefix.
    ///
    /// # Example
    ///
    /// ```rust
    /// use scarab_plugin_api::status_bar::Color;
    ///
    /// let color = Color::Hex("#7aa2f7".to_string());
    /// ```
    Hex(String),

    /// Named color from theme or CSS color names
    ///
    /// Supports standard CSS color names like "red", "blue", "darkgray", etc.
    ///
    /// # Example
    ///
    /// ```rust
    /// use scarab_plugin_api::status_bar::Color;
    ///
    /// let color = Color::Named("cornflowerblue".to_string());
    /// ```
    Named(String),
}

/// Standard 16-color ANSI palette
///
/// Provides the standard ANSI color set with both normal and bright variants.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum AnsiColor {
    /// Standard black (typically #000000)
    Black,
    /// Standard red (typically #800000)
    Red,
    /// Standard green (typically #008000)
    Green,
    /// Standard yellow (typically #808000)
    Yellow,
    /// Standard blue (typically #000080)
    Blue,
    /// Standard magenta (typically #800080)
    Magenta,
    /// Standard cyan (typically #008080)
    Cyan,
    /// Standard white (typically #c0c0c0)
    White,
    /// Bright/bold black, also known as gray (typically #808080)
    BrightBlack,
    /// Bright/bold red (typically #ff0000)
    BrightRed,
    /// Bright/bold green (typically #00ff00)
    BrightGreen,
    /// Bright/bold yellow (typically #ffff00)
    BrightYellow,
    /// Bright/bold blue (typically #0000ff)
    BrightBlue,
    /// Bright/bold magenta (typically #ff00ff)
    BrightMagenta,
    /// Bright/bold cyan (typically #00ffff)
    BrightCyan,
    /// Bright/bold white (typically #ffffff)
    BrightWhite,
}

impl AnsiColor {
    /// Convert ANSI color to RGB values
    ///
    /// Returns a tuple of (r, g, b) values for the color.
    /// Uses a standard terminal color palette.
    ///
    /// # Example
    ///
    /// ```rust
    /// use scarab_plugin_api::status_bar::AnsiColor;
    ///
    /// let (r, g, b) = AnsiColor::BrightBlue.to_rgb();
    /// assert_eq!((r, g, b), (0, 0, 255));
    /// ```
    pub fn to_rgb(&self) -> (u8, u8, u8) {
        match self {
            AnsiColor::Black => (0, 0, 0),
            AnsiColor::Red => (128, 0, 0),
            AnsiColor::Green => (0, 128, 0),
            AnsiColor::Yellow => (128, 128, 0),
            AnsiColor::Blue => (0, 0, 128),
            AnsiColor::Magenta => (128, 0, 128),
            AnsiColor::Cyan => (0, 128, 128),
            AnsiColor::White => (192, 192, 192),
            AnsiColor::BrightBlack => (128, 128, 128),
            AnsiColor::BrightRed => (255, 0, 0),
            AnsiColor::BrightGreen => (0, 255, 0),
            AnsiColor::BrightYellow => (255, 255, 0),
            AnsiColor::BrightBlue => (0, 0, 255),
            AnsiColor::BrightMagenta => (255, 0, 255),
            AnsiColor::BrightCyan => (0, 255, 255),
            AnsiColor::BrightWhite => (255, 255, 255),
        }
    }
}

/// Underline style options
///
/// Defines different visual styles for underlined text.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum UnderlineStyle {
    /// Single straight line
    Single,
    /// Double straight lines
    Double,
    /// Wavy/curly line (often used for spelling errors)
    Curly,
    /// Dotted line
    Dotted,
    /// Dashed line
    Dashed,
}

/// Status bar side/position
///
/// Identifies which side of the status bar to update.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum StatusBarSide {
    /// Left side of the status bar
    Left,
    /// Right side of the status bar
    Right,
}

/// IPC message for status bar updates
///
/// Sent from daemon to client (or from plugin to UI) to update
/// status bar content.
///
/// # Example
///
/// ```rust
/// use scarab_plugin_api::status_bar::{StatusBarUpdate, StatusBarSide, RenderItem};
///
/// let update = StatusBarUpdate {
///     window_id: 1,
///     side: StatusBarSide::Right,
///     items: vec![
///         RenderItem::Text("12:34 PM".to_string()),
///     ],
/// };
/// ```
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct StatusBarUpdate {
    /// ID of the window to update
    pub window_id: u64,
    /// Which side of the status bar to update
    pub side: StatusBarSide,
    /// Render items to display
    pub items: Vec<RenderItem>,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_ansi_color_to_rgb() {
        assert_eq!(AnsiColor::Black.to_rgb(), (0, 0, 0));
        assert_eq!(AnsiColor::Red.to_rgb(), (128, 0, 0));
        assert_eq!(AnsiColor::BrightRed.to_rgb(), (255, 0, 0));
        assert_eq!(AnsiColor::BrightWhite.to_rgb(), (255, 255, 255));
    }

    #[test]
    fn test_render_item_serialization() {
        let item = RenderItem::Text("Hello".to_string());
        let json = serde_json::to_string(&item).unwrap();
        let deserialized: RenderItem = serde_json::from_str(&json).unwrap();

        match deserialized {
            RenderItem::Text(s) => assert_eq!(s, "Hello"),
            _ => panic!("Expected Text variant"),
        }
    }

    #[test]
    fn test_color_serialization() {
        let color = Color::Rgb(255, 128, 64);
        let json = serde_json::to_string(&color).unwrap();
        let deserialized: Color = serde_json::from_str(&json).unwrap();

        match deserialized {
            Color::Rgb(r, g, b) => {
                assert_eq!(r, 255);
                assert_eq!(g, 128);
                assert_eq!(b, 64);
            }
            _ => panic!("Expected Rgb variant"),
        }
    }

    #[test]
    fn test_status_bar_update() {
        let update = StatusBarUpdate {
            window_id: 42,
            side: StatusBarSide::Left,
            items: vec![RenderItem::Bold, RenderItem::Text("Test".to_string())],
        };

        assert_eq!(update.window_id, 42);
        assert_eq!(update.items.len(), 2);
    }

    #[test]
    fn test_complex_status_bar_styling() {
        let items = vec![
            RenderItem::Foreground(Color::Hex("#7aa2f7".to_string())),
            RenderItem::Text("~/project".to_string()),
            RenderItem::ResetForeground,
            RenderItem::Separator(" | ".to_string()),
            RenderItem::Bold,
            RenderItem::Foreground(Color::Named("green".to_string())),
            RenderItem::Text("100%".to_string()),
            RenderItem::ResetAttributes,
        ];

        assert_eq!(items.len(), 8);

        // Verify first item
        match &items[0] {
            RenderItem::Foreground(Color::Hex(s)) => assert_eq!(s, "#7aa2f7"),
            _ => panic!("Expected Foreground item"),
        }
    }
}