use ratatui::style::Color;
#[derive(Debug, Clone, Copy, Default)]
#[expect(dead_code)]
pub(crate) enum SpinnerStyle {
#[default]
Stellar,
Braille,
Dots,
}
impl SpinnerStyle {
pub(crate) fn frames(self) -> &'static [&'static str] {
match self {
Self::Stellar => &["✧", "✦", "✶", "✴", "✸", "✴", "✶", "✦"],
Self::Braille => &["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
Self::Dots => &["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"],
}
}
pub(crate) fn frame_at(self, elapsed_ms: u128) -> &'static str {
let frames = self.frames();
let interval = 120u128;
#[expect(clippy::arithmetic_side_effects)]
let idx = (elapsed_ms / interval % frames.len() as u128) as usize;
frames[idx]
}
}
#[derive(Debug, Clone)]
pub(crate) struct Theme {
pub user: Color,
pub assistant: Color,
pub muted: Color,
pub tool: Color,
pub success: Color,
pub warning: Color,
pub error: Color,
pub thinking: Color,
pub border: Color,
pub cursor: Color,
pub diff_added: Color,
pub diff_removed: Color,
pub diff_context: Color,
pub spinner: SpinnerStyle,
}
impl Default for Theme {
fn default() -> Self {
Self {
user: Color::Reset,
assistant: Color::Reset,
muted: Color::DarkGray,
tool: Color::Cyan,
success: Color::Green,
warning: Color::Yellow,
error: Color::Red,
thinking: Color::Magenta,
border: Color::DarkGray,
cursor: Color::Reset,
diff_added: Color::Green,
diff_removed: Color::Red,
diff_context: Color::DarkGray,
spinner: SpinnerStyle::Stellar,
}
}
}
#[cfg(test)]
mod tests {
use ratatui::style::Color;
use super::Theme;
#[test]
fn primary_text_inherits_the_terminal_foreground() {
let theme = Theme::default();
assert_eq!(theme.user, Color::Reset);
assert_eq!(theme.assistant, Color::Reset);
assert_eq!(theme.cursor, Color::Reset);
}
}