Skip to main content

steer_tui/tui/theme/
mod.rs

1//! Theme system for steer-tui
2//!
3//! This module provides a flexible theming system that allows users to customize
4//! the appearance of the TUI without recompilation. Themes are loaded from TOML
5//! files and can be switched at runtime.
6
7use ratatui::style::{Color, Modifier, Style};
8use serde::{Deserialize, Deserializer, Serialize};
9use std::collections::HashMap;
10use std::fmt;
11use syntect::highlighting::ThemeSet;
12use thiserror::Error;
13use tracing::debug;
14
15mod loader;
16
17pub use loader::ThemeLoader;
18
19/// Load syntect theme sets lazily
20static THEME_SET: std::sync::LazyLock<ThemeSet> = std::sync::LazyLock::new(ThemeSet::load_defaults);
21
22/// Errors that can occur during theme operations
23#[derive(Debug, Error)]
24pub enum ThemeError {
25    #[error("IO error: {0}")]
26    Io(#[from] std::io::Error),
27
28    #[error("Parse error: {0}")]
29    Parse(#[from] toml::de::Error),
30
31    #[error("Validation error: {0}")]
32    Validation(String),
33
34    #[error("Color not found in palette: {0}")]
35    ColorNotFound(String),
36
37    #[error("Invalid color value: {0}")]
38    InvalidColor(String),
39}
40
41/// A color value that can be either a palette reference or a direct color
42#[derive(Debug, Clone, Deserialize)]
43#[serde(untagged)]
44pub enum ColorValue {
45    /// Reference to a palette color (e.g., "background", "red")
46    Palette(String),
47    /// Direct color value (e.g., "#ff0000", "red")
48    Direct(String),
49}
50
51/// Style definition for a component
52#[derive(Debug, Clone, Deserialize)]
53pub struct ComponentStyle {
54    pub fg: Option<ColorValue>,
55    pub bg: Option<ColorValue>,
56    #[serde(default)]
57    pub bold: bool,
58    #[serde(default)]
59    pub italic: bool,
60    #[serde(default)]
61    pub underlined: bool,
62}
63
64/// Raw theme as loaded from TOML file
65#[derive(Debug, Clone, Deserialize)]
66pub struct RawTheme {
67    pub name: String,
68    pub palette: HashMap<String, RgbColor>,
69    pub components: HashMap<Component, ComponentStyle>,
70    pub syntax: Option<SyntaxConfig>,
71}
72
73/// Syntax highlighting configuration
74#[derive(Debug, Clone, Deserialize)]
75pub struct SyntaxConfig {
76    /// Name of a built-in syntect theme
77    pub syntect_theme: Option<String>,
78}
79
80pub type Theme = CompiledTheme;
81
82impl Theme {
83    /// Number of blank lines between chat messages
84    pub fn message_spacing(&self) -> u16 {
85        1 // Could later be made configurable from theme file
86    }
87}
88
89/// RGB color that can be deserialized from hex strings
90#[derive(Debug, Clone, Copy)]
91pub struct RgbColor(pub u8, pub u8, pub u8);
92
93impl<'de> Deserialize<'de> for RgbColor {
94    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
95    where
96        D: Deserializer<'de>,
97    {
98        let s = String::deserialize(deserializer)?;
99
100        // Try hex color first
101        if let Some(hex) = s.strip_prefix('#')
102            && hex.len() == 6
103        {
104            let r = u8::from_str_radix(&hex[0..2], 16)
105                .map_err(|_| serde::de::Error::custom(format!("Invalid hex color: {s}")))?;
106            let g = u8::from_str_radix(&hex[2..4], 16)
107                .map_err(|_| serde::de::Error::custom(format!("Invalid hex color: {s}")))?;
108            let b = u8::from_str_radix(&hex[4..6], 16)
109                .map_err(|_| serde::de::Error::custom(format!("Invalid hex color: {s}")))?;
110            return Ok(RgbColor(r, g, b));
111        }
112
113        // Try named colors
114        match s.to_lowercase().as_str() {
115            "black" => Ok(RgbColor(0, 0, 0)),
116            "red" => Ok(RgbColor(255, 0, 0)),
117            "green" => Ok(RgbColor(0, 255, 0)),
118            "yellow" => Ok(RgbColor(255, 255, 0)),
119            "blue" => Ok(RgbColor(0, 0, 255)),
120            "magenta" => Ok(RgbColor(255, 0, 255)),
121            "cyan" => Ok(RgbColor(0, 255, 255)),
122            "white" => Ok(RgbColor(255, 255, 255)),
123            "gray" | "grey" => Ok(RgbColor(128, 128, 128)),
124            "darkgray" | "darkgrey" | "dark_gray" | "dark_grey" => Ok(RgbColor(64, 64, 64)),
125            _ => Err(serde::de::Error::custom(format!("Unknown color: {s}"))),
126        }
127    }
128}
129
130impl From<RgbColor> for Color {
131    fn from(rgb: RgbColor) -> Self {
132        Color::Rgb(rgb.0, rgb.1, rgb.2)
133    }
134}
135
136/// All themeable components in the TUI
137#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Deserialize, Serialize)]
138#[serde(rename_all = "snake_case")]
139pub enum Component {
140    // Status bar
141    StatusBar,
142
143    // Input panel
144    InputPanelBorder,
145    InputPanelBackground,
146    InputPanelBorderActive,
147    InputPanelBorderCommand,
148    InputPanelBorderApproval,
149    InputPanelBorderError,
150    InputPanelBorderEdit,
151    InputPanelLabel,
152    InputPanelLabelActive,
153    InputPanelLabelCommand,
154    InputPanelLabelConfirmExit,
155    InputPanelLabelEdit,
156
157    // Chat list
158    ChatListBorder,
159    ChatListBackground,
160    UserMessage,
161    UserMessageRole,
162    UserMessageAccent,
163    UserMessageEdit,
164    UserMessageEditAccent,
165    AssistantMessage,
166    AssistantMessageRole,
167    AssistantMessageAccent,
168    SystemMessage,
169    SystemMessageRole,
170    SystemMessageAccent,
171
172    // Tool calls
173    ToolAccent,
174    ToolCall,
175    ToolCallBorder,
176    ToolCallHeader,
177    ToolCallId,
178    ToolOutput,
179    ToolSuccess,
180    ToolError,
181
182    // Assistant thoughts
183    ThoughtBox,
184    ThoughtHeader,
185    ThoughtBorder,
186    ThoughtText,
187
188    // Commands
189    CommandPrompt,
190    CommandText,
191    CommandSuccess,
192    CommandError,
193
194    // General
195    ErrorText,
196    ErrorBold,
197    DimText,
198    SelectionHighlight,
199    PlaceholderText,
200
201    // Model info
202    ModelInfo,
203
204    // Queue preview
205    QueuedMessageBorder,
206    QueuedMessageText,
207    QueuedMessageLabel,
208
209    // Notices
210    NoticeInfo,
211    NoticeWarn,
212    NoticeError,
213
214    // Todo items
215    TodoHigh,
216    TodoMedium,
217    TodoLow,
218    TodoPending,
219    TodoInProgress,
220    TodoCompleted,
221
222    // Code editing
223    CodeAddition,
224    CodeDeletion,
225    CodeFilePath,
226
227    // Popup
228    PopupBorder,
229    PopupSelection,
230
231    // Markdown elements
232    MarkdownH1,
233    MarkdownH2,
234    MarkdownH3,
235    MarkdownH4,
236    MarkdownH5,
237    MarkdownH6,
238    MarkdownParagraph,
239    MarkdownBold,
240    MarkdownItalic,
241    MarkdownStrikethrough,
242    MarkdownCode,
243    MarkdownCodeBlock,
244    MarkdownLink,
245    MarkdownBlockquote,
246    MarkdownListBullet,
247    MarkdownListNumber,
248    MarkdownTaskChecked,
249    MarkdownTaskUnchecked,
250
251    // Markdown table elements
252    MarkdownTableBorder,
253    MarkdownTableHeader,
254    MarkdownTableCell,
255
256    // Setup UI components
257    SetupTitle,
258    SetupBorder,
259    SetupBorderActive,
260    SetupHeader,
261    SetupText,
262    SetupHighlight,
263    SetupKeyBinding,
264    SetupProviderName,
265    SetupProviderSelected,
266    SetupStatusActive,
267    SetupStatusInactive,
268    SetupStatusInProgress,
269    SetupSuccessIcon,
270    SetupErrorMessage,
271    SetupHint,
272    SetupUrl,
273    SetupInputLabel,
274    SetupInputValue,
275    SetupBigText,
276}
277
278impl fmt::Display for Component {
279    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
280        write!(f, "{self:?}")
281    }
282}
283
284/// Compiled theme ready for use in the TUI
285#[derive(Debug, Clone)]
286pub struct CompiledTheme {
287    pub name: String,
288    pub styles: HashMap<Component, Style>,
289    pub background_color: Option<Color>,
290    pub syntax_theme: Option<syntect::highlighting::Theme>,
291}
292
293impl RawTheme {
294    /// Compile the theme into a usable format
295    pub fn into_theme(self) -> Result<Theme, ThemeError> {
296        let mut styles = HashMap::new();
297
298        // Extract background color from palette if it exists
299        let background_color = self.palette.get("background").map(|&rgb| rgb.into());
300
301        // Load syntect theme if configured
302        let syntax_theme = if let Some(syntax_config) = &self.syntax {
303            debug!("Loading syntect theme from config: {:?}", syntax_config);
304            Some(load_syntect_theme(syntax_config)?)
305        } else {
306            debug!("No syntax config found in theme");
307            None
308        };
309
310        // Build the style for each component
311        for (component, style_def) in &self.components {
312            let mut style = Style::default();
313
314            // Resolve foreground color
315            if let Some(fg) = &style_def.fg {
316                let color = self.resolve_color(fg.clone())?;
317                style = style.fg(color);
318            }
319
320            // Resolve background color
321            if let Some(bg) = &style_def.bg {
322                let color = self.resolve_color(bg.clone())?;
323                style = style.bg(color);
324            }
325
326            // Apply modifiers
327            if style_def.bold {
328                style = style.add_modifier(Modifier::BOLD);
329            }
330            if style_def.italic {
331                style = style.add_modifier(Modifier::ITALIC);
332            }
333            if style_def.underlined {
334                style = style.add_modifier(Modifier::UNDERLINED);
335            }
336
337            styles.insert(*component, style);
338        }
339
340        Ok(Theme {
341            name: self.name,
342            styles,
343            background_color,
344            syntax_theme,
345        })
346    }
347
348    /// Resolve a color value to a ratatui Color
349    fn resolve_color(&self, color_value: ColorValue) -> Result<Color, ThemeError> {
350        match color_value {
351            ColorValue::Palette(name) => {
352                // Look up in palette
353                self.palette
354                    .get(&name)
355                    .map(|&rgb| rgb.into())
356                    .ok_or(ThemeError::ColorNotFound(name))
357            }
358            ColorValue::Direct(color_str) => {
359                // Parse as direct color
360                parse_direct_color(&color_str)
361            }
362        }
363    }
364}
365
366/// Load a syntect theme based on configuration
367fn load_syntect_theme(config: &SyntaxConfig) -> Result<syntect::highlighting::Theme, ThemeError> {
368    if let Some(theme_name) = &config.syntect_theme {
369        // Try to load from built-in themes
370        THEME_SET.themes.get(theme_name).cloned().ok_or_else(|| {
371            ThemeError::Validation(format!("Syntect theme '{theme_name}' not found"))
372        })
373    } else {
374        // Default to a reasonable theme
375        THEME_SET
376            .themes
377            .get("base16-ocean.dark")
378            .cloned()
379            .ok_or_else(|| ThemeError::Validation("Default syntect theme not found".to_string()))
380    }
381}
382
383fn parse_direct_color(color_str: &str) -> Result<Color, ThemeError> {
384    // Try hex color first
385    if let Some(hex) = color_str.strip_prefix('#')
386        && hex.len() == 6
387    {
388        let r = u8::from_str_radix(&hex[0..2], 16)
389            .map_err(|_| ThemeError::InvalidColor(color_str.to_string()))?;
390        let g = u8::from_str_radix(&hex[2..4], 16)
391            .map_err(|_| ThemeError::InvalidColor(color_str.to_string()))?;
392        let b = u8::from_str_radix(&hex[4..6], 16)
393            .map_err(|_| ThemeError::InvalidColor(color_str.to_string()))?;
394        return Ok(Color::Rgb(r, g, b));
395    }
396
397    // Try named colors
398    match color_str.to_lowercase().as_str() {
399        "black" => Ok(Color::Black),
400        "red" => Ok(Color::Red),
401        "green" => Ok(Color::Green),
402        "yellow" => Ok(Color::Yellow),
403        "blue" => Ok(Color::Blue),
404        "magenta" => Ok(Color::Magenta),
405        "cyan" => Ok(Color::Cyan),
406        "white" => Ok(Color::White),
407        "gray" | "grey" => Ok(Color::Gray),
408        "darkgray" | "darkgrey" | "dark_gray" | "dark_grey" => Ok(Color::DarkGray),
409        "lightred" | "light_red" => Ok(Color::LightRed),
410        "lightgreen" | "light_green" => Ok(Color::LightGreen),
411        "lightyellow" | "light_yellow" => Ok(Color::LightYellow),
412        "lightblue" | "light_blue" => Ok(Color::LightBlue),
413        "lightmagenta" | "light_magenta" => Ok(Color::LightMagenta),
414        "lightcyan" | "light_cyan" => Ok(Color::LightCyan),
415        "reset" => Ok(Color::Reset),
416        _ => Err(ThemeError::InvalidColor(color_str.to_string())),
417    }
418}
419
420impl CompiledTheme {
421    /// Get a style for a component, falling back to default if not found
422    pub fn style(&self, component: Component) -> Style {
423        self.styles.get(&component).copied().unwrap_or_default()
424    }
425
426    /// Get the background color from the theme, if any
427    pub fn get_background_color(&self) -> Option<Color> {
428        self.background_color
429    }
430
431    // Convenience methods for common styles
432    pub fn error_text(&self) -> Style {
433        self.style(Component::ErrorText)
434    }
435
436    pub fn dim_text(&self) -> Style {
437        self.style(Component::DimText)
438    }
439
440    pub fn subtle_text(&self) -> Style {
441        self.style(Component::DimText)
442    }
443
444    pub fn text(&self) -> Style {
445        Style::default()
446    }
447}
448
449impl Default for CompiledTheme {
450    fn default() -> Self {
451        create_default_theme()
452    }
453}
454
455/// Create the default theme based on current hardcoded colors
456fn create_default_theme() -> CompiledTheme {
457    let mut styles = HashMap::new();
458
459    // Define default component styles based on current styles.rs
460    styles.insert(Component::StatusBar, Style::default().fg(Color::LightCyan));
461
462    // Input panel styles
463    styles.insert(
464        Component::InputPanelBorder,
465        Style::default().fg(Color::DarkGray),
466    );
467    styles.insert(
468        Component::InputPanelBackground,
469        Style::default().bg(Color::Rgb(30, 35, 40)),
470    );
471    styles.insert(
472        Component::InputPanelBorderActive,
473        Style::default().fg(Color::Yellow),
474    );
475    styles.insert(
476        Component::InputPanelBorderCommand,
477        Style::default().fg(Color::Cyan),
478    );
479    styles.insert(
480        Component::InputPanelBorderApproval,
481        Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
482    );
483    styles.insert(
484        Component::InputPanelBorderError,
485        Style::default()
486            .fg(Color::LightRed)
487            .add_modifier(Modifier::BOLD),
488    );
489    styles.insert(
490        Component::InputPanelBorderEdit,
491        Style::default().fg(Color::Yellow),
492    );
493    styles.insert(
494        Component::InputPanelLabelEdit,
495        Style::default().fg(Color::Yellow),
496    );
497
498    // Chat list styles
499    styles.insert(
500        Component::ChatListBorder,
501        Style::default().fg(Color::DarkGray),
502    );
503    styles.insert(Component::ChatListBackground, Style::default());
504    styles.insert(
505        Component::UserMessage,
506        Style::default().bg(Color::Rgb(30, 35, 40)),
507    );
508    styles.insert(
509        Component::UserMessageRole,
510        Style::default()
511            .fg(Color::Green)
512            .add_modifier(Modifier::BOLD),
513    );
514    styles.insert(
515        Component::UserMessageAccent,
516        Style::default().fg(Color::Green),
517    );
518    styles.insert(
519        Component::UserMessageEdit,
520        Style::default().bg(Color::Rgb(40, 45, 50)),
521    );
522    styles.insert(
523        Component::UserMessageEditAccent,
524        Style::default().fg(Color::Yellow),
525    );
526    styles.insert(Component::AssistantMessage, Style::default());
527    styles.insert(
528        Component::AssistantMessageRole,
529        Style::default()
530            .fg(Color::Blue)
531            .add_modifier(Modifier::BOLD),
532    );
533    styles.insert(
534        Component::AssistantMessageAccent,
535        Style::default().fg(Color::DarkGray),
536    );
537    styles.insert(Component::SystemMessage, Style::default());
538    styles.insert(
539        Component::SystemMessageRole,
540        Style::default().fg(Color::Yellow),
541    );
542    styles.insert(
543        Component::SystemMessageAccent,
544        Style::default().fg(Color::DarkGray),
545    );
546
547    // Tool styles
548    styles.insert(Component::ToolAccent, Style::default().fg(Color::DarkGray));
549    styles.insert(Component::ToolCall, Style::default().fg(Color::Cyan));
550    styles.insert(Component::ToolCallBorder, Style::default().fg(Color::Cyan));
551    styles.insert(Component::ToolCallHeader, Style::default().fg(Color::Cyan));
552    styles.insert(Component::ToolCallId, Style::default().fg(Color::DarkGray));
553    styles.insert(Component::ToolOutput, Style::default());
554    styles.insert(Component::ToolSuccess, Style::default().fg(Color::Green));
555    styles.insert(Component::ToolError, Style::default().fg(Color::Red));
556
557    // Thought styles
558    styles.insert(Component::ThoughtBox, Style::default().fg(Color::DarkGray));
559    styles.insert(Component::ThoughtHeader, Style::default().fg(Color::Gray));
560    styles.insert(
561        Component::ThoughtBorder,
562        Style::default().fg(Color::DarkGray),
563    );
564    styles.insert(
565        Component::ThoughtText,
566        Style::default()
567            .fg(Color::DarkGray)
568            .add_modifier(Modifier::ITALIC),
569    );
570
571    // Command styles
572    styles.insert(
573        Component::CommandPrompt,
574        Style::default()
575            .fg(Color::Green)
576            .add_modifier(Modifier::BOLD),
577    );
578    styles.insert(Component::CommandText, Style::default().fg(Color::Cyan));
579    styles.insert(Component::CommandSuccess, Style::default().fg(Color::Green));
580    styles.insert(Component::CommandError, Style::default().fg(Color::Red));
581
582    // General styles
583    styles.insert(Component::ErrorText, Style::default().fg(Color::Red));
584    styles.insert(
585        Component::ErrorBold,
586        Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
587    );
588    styles.insert(Component::DimText, Style::default().fg(Color::DarkGray));
589    styles.insert(
590        Component::SelectionHighlight,
591        Style::default()
592            .fg(Color::Yellow)
593            .add_modifier(Modifier::BOLD),
594    );
595    styles.insert(
596        Component::PlaceholderText,
597        Style::default()
598            .fg(Color::DarkGray)
599            .add_modifier(Modifier::ITALIC),
600    );
601
602    // Model info
603    styles.insert(
604        Component::ModelInfo,
605        Style::default().fg(Color::LightMagenta),
606    );
607
608    // Notices
609    styles.insert(Component::NoticeInfo, Style::default().fg(Color::Blue));
610    styles.insert(Component::NoticeWarn, Style::default().fg(Color::Yellow));
611    styles.insert(Component::NoticeError, Style::default().fg(Color::Red));
612
613    // Todo priorities
614    styles.insert(Component::TodoHigh, Style::default().fg(Color::Red));
615    styles.insert(Component::TodoMedium, Style::default().fg(Color::Yellow));
616    styles.insert(Component::TodoLow, Style::default().fg(Color::Green));
617    styles.insert(Component::TodoPending, Style::default().fg(Color::Blue));
618    styles.insert(
619        Component::TodoInProgress,
620        Style::default().fg(Color::Yellow),
621    );
622    styles.insert(Component::TodoCompleted, Style::default().fg(Color::Green));
623
624    // Code editing
625    styles.insert(Component::CodeAddition, Style::default().fg(Color::Green));
626    styles.insert(Component::CodeDeletion, Style::default().fg(Color::Red));
627    styles.insert(Component::CodeFilePath, Style::default().fg(Color::Yellow));
628
629    // Popup
630    styles.insert(Component::PopupBorder, Style::default().fg(Color::White));
631    styles.insert(
632        Component::PopupSelection,
633        Style::default().fg(Color::Yellow).bg(Color::DarkGray),
634    );
635
636    // Markdown styles
637    styles.insert(Component::MarkdownH1, Style::default().fg(Color::Cyan));
638    styles.insert(Component::MarkdownH2, Style::default().fg(Color::Cyan));
639    styles.insert(Component::MarkdownH3, Style::default().fg(Color::Cyan));
640    styles.insert(Component::MarkdownH4, Style::default().fg(Color::LightCyan));
641    styles.insert(Component::MarkdownH5, Style::default().fg(Color::LightCyan));
642    styles.insert(Component::MarkdownH6, Style::default().fg(Color::Gray));
643    styles.insert(Component::MarkdownParagraph, Style::default());
644    styles.insert(Component::MarkdownBold, Style::default());
645    styles.insert(Component::MarkdownItalic, Style::default());
646    styles.insert(Component::MarkdownStrikethrough, Style::default());
647    styles.insert(
648        Component::MarkdownCode,
649        Style::default().fg(Color::White).bg(Color::Black),
650    );
651    styles.insert(
652        Component::MarkdownCodeBlock,
653        Style::default().bg(Color::Black),
654    );
655    styles.insert(Component::MarkdownLink, Style::default().fg(Color::Blue));
656    styles.insert(
657        Component::MarkdownBlockquote,
658        Style::default().fg(Color::Green),
659    );
660    styles.insert(
661        Component::MarkdownListBullet,
662        Style::default().fg(Color::Gray),
663    );
664    styles.insert(
665        Component::MarkdownListNumber,
666        Style::default().fg(Color::LightBlue),
667    );
668
669    // Table styles
670    styles.insert(
671        Component::MarkdownTableBorder,
672        Style::default().fg(Color::DarkGray),
673    );
674    styles.insert(
675        Component::MarkdownTableHeader,
676        Style::default()
677            .fg(Color::Cyan)
678            .add_modifier(Modifier::BOLD),
679    );
680    styles.insert(Component::MarkdownTableCell, Style::default());
681
682    // Task list styles
683    styles.insert(
684        Component::MarkdownTaskChecked,
685        Style::default().fg(Color::Green),
686    );
687    styles.insert(
688        Component::MarkdownTaskUnchecked,
689        Style::default().fg(Color::Gray),
690    );
691
692    // Setup UI styles
693    styles.insert(
694        Component::SetupTitle,
695        Style::default()
696            .fg(Color::Cyan)
697            .add_modifier(Modifier::BOLD),
698    );
699    styles.insert(Component::SetupBorder, Style::default().fg(Color::DarkGray));
700    styles.insert(
701        Component::SetupBorderActive,
702        Style::default().fg(Color::Yellow),
703    );
704    styles.insert(
705        Component::SetupHeader,
706        Style::default()
707            .fg(Color::Cyan)
708            .add_modifier(Modifier::BOLD),
709    );
710    styles.insert(Component::SetupText, Style::default());
711    styles.insert(
712        Component::SetupHighlight,
713        Style::default()
714            .bg(Color::DarkGray)
715            .add_modifier(Modifier::BOLD),
716    );
717    styles.insert(
718        Component::SetupKeyBinding,
719        Style::default()
720            .fg(Color::Green)
721            .add_modifier(Modifier::BOLD),
722    );
723    styles.insert(Component::SetupProviderName, Style::default());
724    styles.insert(
725        Component::SetupProviderSelected,
726        Style::default()
727            .bg(Color::DarkGray)
728            .add_modifier(Modifier::BOLD),
729    );
730    styles.insert(
731        Component::SetupStatusActive,
732        Style::default().fg(Color::Green),
733    );
734    styles.insert(
735        Component::SetupStatusInactive,
736        Style::default().fg(Color::Red),
737    );
738    styles.insert(
739        Component::SetupStatusInProgress,
740        Style::default().fg(Color::Yellow),
741    );
742    styles.insert(
743        Component::SetupSuccessIcon,
744        Style::default()
745            .fg(Color::Green)
746            .add_modifier(Modifier::BOLD),
747    );
748    styles.insert(
749        Component::SetupErrorMessage,
750        Style::default().fg(Color::Red),
751    );
752    styles.insert(Component::SetupHint, Style::default().fg(Color::DarkGray));
753    styles.insert(
754        Component::SetupUrl,
755        Style::default()
756            .fg(Color::Blue)
757            .add_modifier(Modifier::UNDERLINED),
758    );
759    styles.insert(Component::SetupInputLabel, Style::default());
760    styles.insert(
761        Component::SetupInputValue,
762        Style::default().fg(Color::Yellow),
763    );
764    styles.insert(
765        Component::SetupBigText,
766        Style::default()
767            .fg(Color::Cyan)
768            .add_modifier(Modifier::BOLD),
769    );
770
771    CompiledTheme {
772        name: "Default".to_string(),
773        styles,
774        background_color: None, // Default theme has no background color
775        syntax_theme: None,
776    }
777}