scarab_plugin_api/status_bar/mod.rs
1//! Status Bar Rendering API for Scarab terminal emulator
2//!
3//! This module provides types and utilities for programmable status bars.
4//! Similar to WezTerm's status bar API, it allows plugins to dynamically
5//! update status bar content with rich styling and formatting.
6//!
7//! ## Example
8//!
9//! ```rust
10//! use scarab_plugin_api::status_bar::{RenderItem, Color, StatusBarUpdate, StatusBarSide};
11//!
12//! // Create a styled status bar update
13//! let items = vec![
14//! RenderItem::Foreground(Color::Hex("#7aa2f7".to_string())),
15//! RenderItem::Text("~/project".to_string()),
16//! RenderItem::Text(" | ".to_string()),
17//! RenderItem::Bold,
18//! RenderItem::Text("12:34".to_string()),
19//! RenderItem::ResetAttributes,
20//! ];
21//!
22//! let update = StatusBarUpdate {
23//! window_id: 1,
24//! side: StatusBarSide::Right,
25//! items,
26//! };
27//! ```
28
29use serde::{Deserialize, Serialize};
30
31/// A single rendering element for status bar content
32///
33/// RenderItems are processed sequentially to build styled text for the status bar.
34/// They can represent text content, color changes, text attributes, or layout elements.
35#[derive(Clone, Debug, Serialize, Deserialize)]
36#[serde(tag = "type", content = "data")]
37pub enum RenderItem {
38 /// Display text content
39 ///
40 /// Renders the provided string with the current text style.
41 ///
42 /// # Example
43 ///
44 /// ```rust
45 /// use scarab_plugin_api::status_bar::RenderItem;
46 ///
47 /// let item = RenderItem::Text("Hello, World!".to_string());
48 /// ```
49 Text(String),
50
51 /// Display a Nerd Font icon
52 ///
53 /// Renders an icon from the Nerd Font icon set by name.
54 /// The icon will use the current foreground color and attributes.
55 ///
56 /// # Example
57 ///
58 /// ```rust
59 /// use scarab_plugin_api::status_bar::RenderItem;
60 ///
61 /// let item = RenderItem::Icon("nf-fa-battery_full".to_string());
62 /// ```
63 Icon(String),
64
65 /// Set foreground (text) color
66 ///
67 /// Changes the text color for all subsequent text items until
68 /// another color change or reset.
69 ///
70 /// # Example
71 ///
72 /// ```rust
73 /// use scarab_plugin_api::status_bar::{RenderItem, Color};
74 ///
75 /// let item = RenderItem::Foreground(Color::Rgb(122, 162, 247));
76 /// ```
77 Foreground(Color),
78
79 /// Set foreground color using ANSI color
80 ///
81 /// Alternative to `Foreground` using standard ANSI color names.
82 ///
83 /// # Example
84 ///
85 /// ```rust
86 /// use scarab_plugin_api::status_bar::{RenderItem, AnsiColor};
87 ///
88 /// let item = RenderItem::ForegroundAnsi(AnsiColor::BrightBlue);
89 /// ```
90 ForegroundAnsi(AnsiColor),
91
92 /// Set background color
93 ///
94 /// Changes the background color for all subsequent text items.
95 ///
96 /// # Example
97 ///
98 /// ```rust
99 /// use scarab_plugin_api::status_bar::{RenderItem, Color};
100 ///
101 /// let item = RenderItem::Background(Color::Named("darkgray".to_string()));
102 /// ```
103 Background(Color),
104
105 /// Set background color using ANSI color
106 ///
107 /// Alternative to `Background` using standard ANSI color names.
108 ///
109 /// # Example
110 ///
111 /// ```rust
112 /// use scarab_plugin_api::status_bar::{RenderItem, AnsiColor};
113 ///
114 /// let item = RenderItem::BackgroundAnsi(AnsiColor::Black);
115 /// ```
116 BackgroundAnsi(AnsiColor),
117
118 /// Enable bold text attribute
119 ///
120 /// Makes all subsequent text bold until reset or another weight change.
121 ///
122 /// # Example
123 ///
124 /// ```rust
125 /// use scarab_plugin_api::status_bar::RenderItem;
126 ///
127 /// let items = vec![
128 /// RenderItem::Bold,
129 /// RenderItem::Text("Important".to_string()),
130 /// ];
131 /// ```
132 Bold,
133
134 /// Enable italic text attribute
135 ///
136 /// Makes all subsequent text italic until reset.
137 ///
138 /// # Example
139 ///
140 /// ```rust
141 /// use scarab_plugin_api::status_bar::RenderItem;
142 ///
143 /// let items = vec![
144 /// RenderItem::Italic,
145 /// RenderItem::Text("Emphasis".to_string()),
146 /// ];
147 /// ```
148 Italic,
149
150 /// Enable underline with specified style
151 ///
152 /// Underlines all subsequent text with the given style until reset.
153 ///
154 /// # Example
155 ///
156 /// ```rust
157 /// use scarab_plugin_api::status_bar::{RenderItem, UnderlineStyle};
158 ///
159 /// let items = vec![
160 /// RenderItem::Underline(UnderlineStyle::Curly),
161 /// RenderItem::Text("Warning".to_string()),
162 /// ];
163 /// ```
164 Underline(UnderlineStyle),
165
166 /// Enable strikethrough text attribute
167 ///
168 /// Strikes through all subsequent text until reset.
169 ///
170 /// # Example
171 ///
172 /// ```rust
173 /// use scarab_plugin_api::status_bar::RenderItem;
174 ///
175 /// let items = vec![
176 /// RenderItem::Strikethrough,
177 /// RenderItem::Text("Deprecated".to_string()),
178 /// ];
179 /// ```
180 Strikethrough,
181
182 /// Reset all text attributes to defaults
183 ///
184 /// Clears all colors, bold, italic, underline, and strikethrough attributes.
185 ///
186 /// # Example
187 ///
188 /// ```rust
189 /// use scarab_plugin_api::status_bar::RenderItem;
190 ///
191 /// let items = vec![
192 /// RenderItem::Bold,
193 /// RenderItem::Text("Bold".to_string()),
194 /// RenderItem::ResetAttributes,
195 /// RenderItem::Text("Normal".to_string()),
196 /// ];
197 /// ```
198 ResetAttributes,
199
200 /// Reset foreground color to default
201 ///
202 /// Clears only the foreground color, keeping other attributes.
203 ///
204 /// # Example
205 ///
206 /// ```rust
207 /// use scarab_plugin_api::status_bar::{RenderItem, Color};
208 ///
209 /// let items = vec![
210 /// RenderItem::Bold,
211 /// RenderItem::Foreground(Color::Hex("#ff0000".to_string())),
212 /// RenderItem::Text("Red & Bold".to_string()),
213 /// RenderItem::ResetForeground,
214 /// RenderItem::Text("Default color, still bold".to_string()),
215 /// ];
216 /// ```
217 ResetForeground,
218
219 /// Reset background color to default
220 ///
221 /// Clears only the background color, keeping other attributes.
222 ///
223 /// # Example
224 ///
225 /// ```rust
226 /// use scarab_plugin_api::status_bar::{RenderItem, Color};
227 ///
228 /// let items = vec![
229 /// RenderItem::Background(Color::Hex("#333333".to_string())),
230 /// RenderItem::Text("Dark background".to_string()),
231 /// RenderItem::ResetBackground,
232 /// RenderItem::Text("Default background".to_string()),
233 /// ];
234 /// ```
235 ResetBackground,
236
237 /// Insert flexible spacing
238 ///
239 /// Creates space that expands to fill available room.
240 /// Useful for pushing content to opposite ends of the status bar.
241 ///
242 /// # Example
243 ///
244 /// ```rust
245 /// use scarab_plugin_api::status_bar::RenderItem;
246 ///
247 /// let items = vec![
248 /// RenderItem::Text("Left".to_string()),
249 /// RenderItem::Spacer,
250 /// RenderItem::Text("Right".to_string()),
251 /// ];
252 /// ```
253 Spacer,
254
255 /// Insert fixed spacing
256 ///
257 /// Adds a fixed number of space characters (cells).
258 ///
259 /// # Example
260 ///
261 /// ```rust
262 /// use scarab_plugin_api::status_bar::RenderItem;
263 ///
264 /// let items = vec![
265 /// RenderItem::Text("A".to_string()),
266 /// RenderItem::Padding(3),
267 /// RenderItem::Text("B".to_string()),
268 /// ];
269 /// ```
270 Padding(u8),
271
272 /// Insert a separator string
273 ///
274 /// Convenience item for common separators like " | " or " • ".
275 ///
276 /// # Example
277 ///
278 /// ```rust
279 /// use scarab_plugin_api::status_bar::RenderItem;
280 ///
281 /// let items = vec![
282 /// RenderItem::Text("Section 1".to_string()),
283 /// RenderItem::Separator(" | ".to_string()),
284 /// RenderItem::Text("Section 2".to_string()),
285 /// ];
286 /// ```
287 Separator(String),
288}
289
290/// Color specification for text rendering
291///
292/// Supports RGB values, hex notation, and named colors.
293#[derive(Clone, Debug, Serialize, Deserialize)]
294#[serde(tag = "type", content = "value")]
295pub enum Color {
296 /// RGB color with 8-bit components
297 ///
298 /// # Example
299 ///
300 /// ```rust
301 /// use scarab_plugin_api::status_bar::Color;
302 ///
303 /// let color = Color::Rgb(122, 162, 247); // Light blue
304 /// ```
305 Rgb(u8, u8, u8),
306
307 /// Hexadecimal color string
308 ///
309 /// Supports standard hex color notation with or without '#' prefix.
310 ///
311 /// # Example
312 ///
313 /// ```rust
314 /// use scarab_plugin_api::status_bar::Color;
315 ///
316 /// let color = Color::Hex("#7aa2f7".to_string());
317 /// ```
318 Hex(String),
319
320 /// Named color from theme or CSS color names
321 ///
322 /// Supports standard CSS color names like "red", "blue", "darkgray", etc.
323 ///
324 /// # Example
325 ///
326 /// ```rust
327 /// use scarab_plugin_api::status_bar::Color;
328 ///
329 /// let color = Color::Named("cornflowerblue".to_string());
330 /// ```
331 Named(String),
332}
333
334/// Standard 16-color ANSI palette
335///
336/// Provides the standard ANSI color set with both normal and bright variants.
337#[derive(Clone, Debug, Serialize, Deserialize)]
338pub enum AnsiColor {
339 /// Standard black (typically #000000)
340 Black,
341 /// Standard red (typically #800000)
342 Red,
343 /// Standard green (typically #008000)
344 Green,
345 /// Standard yellow (typically #808000)
346 Yellow,
347 /// Standard blue (typically #000080)
348 Blue,
349 /// Standard magenta (typically #800080)
350 Magenta,
351 /// Standard cyan (typically #008080)
352 Cyan,
353 /// Standard white (typically #c0c0c0)
354 White,
355 /// Bright/bold black, also known as gray (typically #808080)
356 BrightBlack,
357 /// Bright/bold red (typically #ff0000)
358 BrightRed,
359 /// Bright/bold green (typically #00ff00)
360 BrightGreen,
361 /// Bright/bold yellow (typically #ffff00)
362 BrightYellow,
363 /// Bright/bold blue (typically #0000ff)
364 BrightBlue,
365 /// Bright/bold magenta (typically #ff00ff)
366 BrightMagenta,
367 /// Bright/bold cyan (typically #00ffff)
368 BrightCyan,
369 /// Bright/bold white (typically #ffffff)
370 BrightWhite,
371}
372
373impl AnsiColor {
374 /// Convert ANSI color to RGB values
375 ///
376 /// Returns a tuple of (r, g, b) values for the color.
377 /// Uses a standard terminal color palette.
378 ///
379 /// # Example
380 ///
381 /// ```rust
382 /// use scarab_plugin_api::status_bar::AnsiColor;
383 ///
384 /// let (r, g, b) = AnsiColor::BrightBlue.to_rgb();
385 /// assert_eq!((r, g, b), (0, 0, 255));
386 /// ```
387 pub fn to_rgb(&self) -> (u8, u8, u8) {
388 match self {
389 AnsiColor::Black => (0, 0, 0),
390 AnsiColor::Red => (128, 0, 0),
391 AnsiColor::Green => (0, 128, 0),
392 AnsiColor::Yellow => (128, 128, 0),
393 AnsiColor::Blue => (0, 0, 128),
394 AnsiColor::Magenta => (128, 0, 128),
395 AnsiColor::Cyan => (0, 128, 128),
396 AnsiColor::White => (192, 192, 192),
397 AnsiColor::BrightBlack => (128, 128, 128),
398 AnsiColor::BrightRed => (255, 0, 0),
399 AnsiColor::BrightGreen => (0, 255, 0),
400 AnsiColor::BrightYellow => (255, 255, 0),
401 AnsiColor::BrightBlue => (0, 0, 255),
402 AnsiColor::BrightMagenta => (255, 0, 255),
403 AnsiColor::BrightCyan => (0, 255, 255),
404 AnsiColor::BrightWhite => (255, 255, 255),
405 }
406 }
407}
408
409/// Underline style options
410///
411/// Defines different visual styles for underlined text.
412#[derive(Clone, Debug, Serialize, Deserialize)]
413pub enum UnderlineStyle {
414 /// Single straight line
415 Single,
416 /// Double straight lines
417 Double,
418 /// Wavy/curly line (often used for spelling errors)
419 Curly,
420 /// Dotted line
421 Dotted,
422 /// Dashed line
423 Dashed,
424}
425
426/// Status bar side/position
427///
428/// Identifies which side of the status bar to update.
429#[derive(Clone, Debug, Serialize, Deserialize)]
430pub enum StatusBarSide {
431 /// Left side of the status bar
432 Left,
433 /// Right side of the status bar
434 Right,
435}
436
437/// IPC message for status bar updates
438///
439/// Sent from daemon to client (or from plugin to UI) to update
440/// status bar content.
441///
442/// # Example
443///
444/// ```rust
445/// use scarab_plugin_api::status_bar::{StatusBarUpdate, StatusBarSide, RenderItem};
446///
447/// let update = StatusBarUpdate {
448/// window_id: 1,
449/// side: StatusBarSide::Right,
450/// items: vec![
451/// RenderItem::Text("12:34 PM".to_string()),
452/// ],
453/// };
454/// ```
455#[derive(Clone, Debug, Serialize, Deserialize)]
456pub struct StatusBarUpdate {
457 /// ID of the window to update
458 pub window_id: u64,
459 /// Which side of the status bar to update
460 pub side: StatusBarSide,
461 /// Render items to display
462 pub items: Vec<RenderItem>,
463}
464
465#[cfg(test)]
466mod tests {
467 use super::*;
468
469 #[test]
470 fn test_ansi_color_to_rgb() {
471 assert_eq!(AnsiColor::Black.to_rgb(), (0, 0, 0));
472 assert_eq!(AnsiColor::Red.to_rgb(), (128, 0, 0));
473 assert_eq!(AnsiColor::BrightRed.to_rgb(), (255, 0, 0));
474 assert_eq!(AnsiColor::BrightWhite.to_rgb(), (255, 255, 255));
475 }
476
477 #[test]
478 fn test_render_item_serialization() {
479 let item = RenderItem::Text("Hello".to_string());
480 let json = serde_json::to_string(&item).unwrap();
481 let deserialized: RenderItem = serde_json::from_str(&json).unwrap();
482
483 match deserialized {
484 RenderItem::Text(s) => assert_eq!(s, "Hello"),
485 _ => panic!("Expected Text variant"),
486 }
487 }
488
489 #[test]
490 fn test_color_serialization() {
491 let color = Color::Rgb(255, 128, 64);
492 let json = serde_json::to_string(&color).unwrap();
493 let deserialized: Color = serde_json::from_str(&json).unwrap();
494
495 match deserialized {
496 Color::Rgb(r, g, b) => {
497 assert_eq!(r, 255);
498 assert_eq!(g, 128);
499 assert_eq!(b, 64);
500 }
501 _ => panic!("Expected Rgb variant"),
502 }
503 }
504
505 #[test]
506 fn test_status_bar_update() {
507 let update = StatusBarUpdate {
508 window_id: 42,
509 side: StatusBarSide::Left,
510 items: vec![RenderItem::Bold, RenderItem::Text("Test".to_string())],
511 };
512
513 assert_eq!(update.window_id, 42);
514 assert_eq!(update.items.len(), 2);
515 }
516
517 #[test]
518 fn test_complex_status_bar_styling() {
519 let items = vec![
520 RenderItem::Foreground(Color::Hex("#7aa2f7".to_string())),
521 RenderItem::Text("~/project".to_string()),
522 RenderItem::ResetForeground,
523 RenderItem::Separator(" | ".to_string()),
524 RenderItem::Bold,
525 RenderItem::Foreground(Color::Named("green".to_string())),
526 RenderItem::Text("100%".to_string()),
527 RenderItem::ResetAttributes,
528 ];
529
530 assert_eq!(items.len(), 8);
531
532 // Verify first item
533 match &items[0] {
534 RenderItem::Foreground(Color::Hex(s)) => assert_eq!(s, "#7aa2f7"),
535 _ => panic!("Expected Foreground item"),
536 }
537 }
538}