Add `focus_beacon_span`, `panel_title_spans`, `panel_hint_style`, and `panel_border_style` utilities to centralize panel header, hint, border, and beacon rendering. Integrate these helpers across all UI panels (files, chat, thinking, agent actions, input, status bar) and update help text. Extend `Theme` with new color fields for beacons, pane headers, and hint text, providing defaults for all built‑in themes. Include comprehensive unit tests for the new styling functions.
1230 lines
49 KiB
Rust
1230 lines
49 KiB
Rust
//! Theming system for OWLEN TUI
|
|
//!
|
|
//! Provides customizable color schemes for all UI components.
|
|
|
|
use ratatui::style::Color;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
use std::fs;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
pub type ThemePalette = Theme;
|
|
|
|
/// A complete theme definition for OWLEN TUI
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Theme {
|
|
/// Name of the theme
|
|
pub name: String,
|
|
|
|
/// Default text color
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub text: Color,
|
|
|
|
/// Default background color
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub background: Color,
|
|
|
|
/// Border color for focused panels
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub focused_panel_border: Color,
|
|
|
|
/// Border color for unfocused panels
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub unfocused_panel_border: Color,
|
|
|
|
/// Foreground color for the active pane beacon (`▌`)
|
|
#[serde(default = "Theme::default_focus_beacon_fg")]
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub focus_beacon_fg: Color,
|
|
|
|
/// Background color for the active pane beacon (`▌`)
|
|
#[serde(default = "Theme::default_focus_beacon_bg")]
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub focus_beacon_bg: Color,
|
|
|
|
/// Foreground color for the inactive pane beacon (`▌`)
|
|
#[serde(default = "Theme::default_unfocused_beacon_fg")]
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub unfocused_beacon_fg: Color,
|
|
|
|
/// Title color for active pane headers
|
|
#[serde(default = "Theme::default_pane_header_active")]
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub pane_header_active: Color,
|
|
|
|
/// Title color for inactive pane headers
|
|
#[serde(default = "Theme::default_pane_header_inactive")]
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub pane_header_inactive: Color,
|
|
|
|
/// Hint text color used within pane headers
|
|
#[serde(default = "Theme::default_pane_hint_text")]
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub pane_hint_text: Color,
|
|
|
|
/// Color for user message role indicator
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub user_message_role: Color,
|
|
|
|
/// Color for assistant message role indicator
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub assistant_message_role: Color,
|
|
|
|
/// Color for tool output messages
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub tool_output: Color,
|
|
|
|
/// Color for thinking panel title
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub thinking_panel_title: Color,
|
|
|
|
/// Background color for command bar
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub command_bar_background: Color,
|
|
|
|
/// Status line background color
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub status_background: Color,
|
|
|
|
/// Color for Normal mode indicator
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub mode_normal: Color,
|
|
|
|
/// Color for Editing mode indicator
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub mode_editing: Color,
|
|
|
|
/// Color for Model Selection mode indicator
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub mode_model_selection: Color,
|
|
|
|
/// Color for Provider Selection mode indicator
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub mode_provider_selection: Color,
|
|
|
|
/// Color for Help mode indicator
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub mode_help: Color,
|
|
|
|
/// Color for Visual mode indicator
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub mode_visual: Color,
|
|
|
|
/// Color for Command mode indicator
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub mode_command: Color,
|
|
|
|
/// Selection/highlight background color
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub selection_bg: Color,
|
|
|
|
/// Selection/highlight foreground color
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub selection_fg: Color,
|
|
|
|
/// Cursor indicator color
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub cursor: Color,
|
|
|
|
/// Code block background color
|
|
#[serde(default = "Theme::default_code_block_background")]
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub code_block_background: Color,
|
|
|
|
/// Code block border color
|
|
#[serde(default = "Theme::default_code_block_border")]
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub code_block_border: Color,
|
|
|
|
/// Code block text color
|
|
#[serde(default = "Theme::default_code_block_text")]
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub code_block_text: Color,
|
|
|
|
/// Code block keyword color
|
|
#[serde(default = "Theme::default_code_block_keyword")]
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub code_block_keyword: Color,
|
|
|
|
/// Code block string literal color
|
|
#[serde(default = "Theme::default_code_block_string")]
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub code_block_string: Color,
|
|
|
|
/// Code block comment color
|
|
#[serde(default = "Theme::default_code_block_comment")]
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub code_block_comment: Color,
|
|
|
|
/// Placeholder text color
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub placeholder: Color,
|
|
|
|
/// Warning/error message color
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub error: Color,
|
|
|
|
/// Success/info message color
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub info: Color,
|
|
|
|
/// Agent action coloring (ReAct THOUGHT)
|
|
#[serde(default = "Theme::default_agent_thought")]
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub agent_thought: Color,
|
|
|
|
/// Agent action coloring (ReAct ACTION)
|
|
#[serde(default = "Theme::default_agent_action")]
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub agent_action: Color,
|
|
|
|
/// Agent action coloring (ReAct ACTION_INPUT)
|
|
#[serde(default = "Theme::default_agent_action_input")]
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub agent_action_input: Color,
|
|
|
|
/// Agent action coloring (ReAct OBSERVATION)
|
|
#[serde(default = "Theme::default_agent_observation")]
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub agent_observation: Color,
|
|
|
|
/// Agent action coloring (ReAct FINAL_ANSWER)
|
|
#[serde(default = "Theme::default_agent_final_answer")]
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub agent_final_answer: Color,
|
|
|
|
/// Status badge foreground when agent is running
|
|
#[serde(default = "Theme::default_agent_badge_running_fg")]
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub agent_badge_running_fg: Color,
|
|
|
|
/// Status badge background when agent is running
|
|
#[serde(default = "Theme::default_agent_badge_running_bg")]
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub agent_badge_running_bg: Color,
|
|
|
|
/// Status badge foreground when agent mode is idle
|
|
#[serde(default = "Theme::default_agent_badge_idle_fg")]
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub agent_badge_idle_fg: Color,
|
|
|
|
/// Status badge background when agent mode is idle
|
|
#[serde(default = "Theme::default_agent_badge_idle_bg")]
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub agent_badge_idle_bg: Color,
|
|
|
|
/// Operating mode badge foreground (Chat)
|
|
#[serde(default = "Theme::default_operating_chat_fg")]
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub operating_chat_fg: Color,
|
|
|
|
/// Operating mode badge background (Chat)
|
|
#[serde(default = "Theme::default_operating_chat_bg")]
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub operating_chat_bg: Color,
|
|
|
|
/// Operating mode badge foreground (Code)
|
|
#[serde(default = "Theme::default_operating_code_fg")]
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub operating_code_fg: Color,
|
|
|
|
/// Operating mode badge background (Code)
|
|
#[serde(default = "Theme::default_operating_code_bg")]
|
|
#[serde(deserialize_with = "deserialize_color")]
|
|
#[serde(serialize_with = "serialize_color")]
|
|
pub operating_code_bg: Color,
|
|
}
|
|
|
|
impl Default for Theme {
|
|
fn default() -> Self {
|
|
default_dark()
|
|
}
|
|
}
|
|
|
|
impl Theme {
|
|
const fn default_code_block_background() -> Color {
|
|
Color::Black
|
|
}
|
|
|
|
const fn default_code_block_border() -> Color {
|
|
Color::Gray
|
|
}
|
|
|
|
const fn default_code_block_text() -> Color {
|
|
Color::White
|
|
}
|
|
|
|
const fn default_code_block_keyword() -> Color {
|
|
Color::Yellow
|
|
}
|
|
|
|
const fn default_code_block_string() -> Color {
|
|
Color::LightGreen
|
|
}
|
|
|
|
const fn default_code_block_comment() -> Color {
|
|
Color::DarkGray
|
|
}
|
|
|
|
const fn default_agent_thought() -> Color {
|
|
Color::LightBlue
|
|
}
|
|
|
|
const fn default_agent_action() -> Color {
|
|
Color::Yellow
|
|
}
|
|
|
|
const fn default_agent_action_input() -> Color {
|
|
Color::LightCyan
|
|
}
|
|
|
|
const fn default_agent_observation() -> Color {
|
|
Color::LightGreen
|
|
}
|
|
|
|
const fn default_agent_final_answer() -> Color {
|
|
Color::Magenta
|
|
}
|
|
|
|
const fn default_agent_badge_running_fg() -> Color {
|
|
Color::Black
|
|
}
|
|
|
|
const fn default_agent_badge_running_bg() -> Color {
|
|
Color::Yellow
|
|
}
|
|
|
|
const fn default_agent_badge_idle_fg() -> Color {
|
|
Color::Black
|
|
}
|
|
|
|
const fn default_agent_badge_idle_bg() -> Color {
|
|
Color::Cyan
|
|
}
|
|
|
|
const fn default_focus_beacon_fg() -> Color {
|
|
Color::LightMagenta
|
|
}
|
|
|
|
const fn default_focus_beacon_bg() -> Color {
|
|
Color::Black
|
|
}
|
|
|
|
const fn default_unfocused_beacon_fg() -> Color {
|
|
Color::DarkGray
|
|
}
|
|
|
|
const fn default_pane_header_active() -> Color {
|
|
Color::White
|
|
}
|
|
|
|
const fn default_pane_header_inactive() -> Color {
|
|
Color::Gray
|
|
}
|
|
|
|
const fn default_pane_hint_text() -> Color {
|
|
Color::DarkGray
|
|
}
|
|
|
|
const fn default_operating_chat_fg() -> Color {
|
|
Color::Black
|
|
}
|
|
|
|
const fn default_operating_chat_bg() -> Color {
|
|
Color::Blue
|
|
}
|
|
|
|
const fn default_operating_code_fg() -> Color {
|
|
Color::Black
|
|
}
|
|
|
|
const fn default_operating_code_bg() -> Color {
|
|
Color::Magenta
|
|
}
|
|
}
|
|
|
|
/// Get the default themes directory path
|
|
pub fn default_themes_dir() -> PathBuf {
|
|
let config_dir = PathBuf::from(shellexpand::tilde(crate::config::DEFAULT_CONFIG_PATH).as_ref())
|
|
.parent()
|
|
.map(|p| p.to_path_buf())
|
|
.unwrap_or_else(|| PathBuf::from("~/.config/owlen"));
|
|
|
|
config_dir.join("themes")
|
|
}
|
|
|
|
/// Load all available themes (built-in + custom)
|
|
pub fn load_all_themes() -> HashMap<String, Theme> {
|
|
let mut themes = HashMap::new();
|
|
|
|
// Load built-in themes
|
|
for (name, theme) in built_in_themes() {
|
|
themes.insert(name, theme);
|
|
}
|
|
|
|
// Load custom themes from disk
|
|
let themes_dir = default_themes_dir();
|
|
if let Ok(entries) = fs::read_dir(&themes_dir) {
|
|
for entry in entries.flatten() {
|
|
let path = entry.path();
|
|
if path.extension().and_then(|s| s.to_str()) == Some("toml") {
|
|
let name = path
|
|
.file_stem()
|
|
.and_then(|s| s.to_str())
|
|
.unwrap_or("unknown")
|
|
.to_string();
|
|
|
|
match load_theme_from_file(&path) {
|
|
Ok(theme) => {
|
|
themes.insert(name.clone(), theme);
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Warning: Failed to load custom theme '{}': {}", name, e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
themes
|
|
}
|
|
|
|
/// Load a theme from a TOML file
|
|
pub fn load_theme_from_file(path: &Path) -> Result<Theme, String> {
|
|
let content =
|
|
fs::read_to_string(path).map_err(|e| format!("Failed to read theme file: {}", e))?;
|
|
|
|
toml::from_str(&content).map_err(|e| format!("Failed to parse theme file: {}", e))
|
|
}
|
|
|
|
/// Get a theme by name (built-in or custom)
|
|
pub fn get_theme(name: &str) -> Option<Theme> {
|
|
load_all_themes().get(name).cloned()
|
|
}
|
|
|
|
/// Get all built-in themes (embedded in the binary)
|
|
pub fn built_in_themes() -> HashMap<String, Theme> {
|
|
let mut themes = HashMap::new();
|
|
|
|
// Load embedded theme files
|
|
let embedded_themes = [
|
|
(
|
|
"default_dark",
|
|
include_str!("../../../themes/default_dark.toml"),
|
|
),
|
|
(
|
|
"default_light",
|
|
include_str!("../../../themes/default_light.toml"),
|
|
),
|
|
(
|
|
"ansi_basic",
|
|
include_str!("../../../themes/ansi-basic.toml"),
|
|
),
|
|
(
|
|
"grayscale-high-contrast",
|
|
include_str!("../../../themes/grayscale-high-contrast.toml"),
|
|
),
|
|
("gruvbox", include_str!("../../../themes/gruvbox.toml")),
|
|
("dracula", include_str!("../../../themes/dracula.toml")),
|
|
("solarized", include_str!("../../../themes/solarized.toml")),
|
|
(
|
|
"midnight-ocean",
|
|
include_str!("../../../themes/midnight-ocean.toml"),
|
|
),
|
|
("rose-pine", include_str!("../../../themes/rose-pine.toml")),
|
|
("monokai", include_str!("../../../themes/monokai.toml")),
|
|
(
|
|
"material-dark",
|
|
include_str!("../../../themes/material-dark.toml"),
|
|
),
|
|
(
|
|
"material-light",
|
|
include_str!("../../../themes/material-light.toml"),
|
|
),
|
|
];
|
|
|
|
for (name, content) in embedded_themes {
|
|
match toml::from_str::<Theme>(content) {
|
|
Ok(theme) => {
|
|
themes.insert(name.to_string(), theme);
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Warning: Failed to parse built-in theme '{}': {}", name, e);
|
|
// Fallback to hardcoded version if parsing fails
|
|
if let Some(fallback) = get_fallback_theme(name) {
|
|
themes.insert(name.to_string(), fallback);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
themes
|
|
}
|
|
|
|
/// Get fallback hardcoded theme (used if embedded TOML fails to parse)
|
|
fn get_fallback_theme(name: &str) -> Option<Theme> {
|
|
match name {
|
|
"default_dark" => Some(default_dark()),
|
|
"default_light" => Some(default_light()),
|
|
"gruvbox" => Some(gruvbox()),
|
|
"dracula" => Some(dracula()),
|
|
"solarized" => Some(solarized()),
|
|
"midnight-ocean" => Some(midnight_ocean()),
|
|
"rose-pine" => Some(rose_pine()),
|
|
"monokai" => Some(monokai()),
|
|
"material-dark" => Some(material_dark()),
|
|
"material-light" => Some(material_light()),
|
|
"grayscale-high-contrast" => Some(grayscale_high_contrast()),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// Default dark theme
|
|
fn default_dark() -> Theme {
|
|
Theme {
|
|
name: "default_dark".to_string(),
|
|
text: Color::White,
|
|
background: Color::Black,
|
|
focused_panel_border: Color::LightMagenta,
|
|
unfocused_panel_border: Color::Rgb(95, 20, 135),
|
|
focus_beacon_fg: Theme::default_focus_beacon_fg(),
|
|
focus_beacon_bg: Theme::default_focus_beacon_bg(),
|
|
unfocused_beacon_fg: Theme::default_unfocused_beacon_fg(),
|
|
pane_header_active: Theme::default_pane_header_active(),
|
|
pane_header_inactive: Theme::default_pane_header_inactive(),
|
|
pane_hint_text: Theme::default_pane_hint_text(),
|
|
user_message_role: Color::LightBlue,
|
|
assistant_message_role: Color::Yellow,
|
|
tool_output: Color::Gray,
|
|
thinking_panel_title: Color::LightMagenta,
|
|
command_bar_background: Color::Black,
|
|
status_background: Color::Black,
|
|
mode_normal: Color::LightBlue,
|
|
mode_editing: Color::LightGreen,
|
|
mode_model_selection: Color::LightYellow,
|
|
mode_provider_selection: Color::LightCyan,
|
|
mode_help: Color::LightMagenta,
|
|
mode_visual: Color::Magenta,
|
|
mode_command: Color::Yellow,
|
|
selection_bg: Color::LightBlue,
|
|
selection_fg: Color::Black,
|
|
cursor: Color::Magenta,
|
|
code_block_background: Color::Rgb(25, 25, 25),
|
|
code_block_border: Color::LightMagenta,
|
|
code_block_text: Color::White,
|
|
code_block_keyword: Color::Yellow,
|
|
code_block_string: Color::LightGreen,
|
|
code_block_comment: Color::Gray,
|
|
placeholder: Color::DarkGray,
|
|
error: Color::Red,
|
|
info: Color::LightGreen,
|
|
agent_thought: Color::LightBlue,
|
|
agent_action: Color::Yellow,
|
|
agent_action_input: Color::LightCyan,
|
|
agent_observation: Color::LightGreen,
|
|
agent_final_answer: Color::Magenta,
|
|
agent_badge_running_fg: Color::Black,
|
|
agent_badge_running_bg: Color::Yellow,
|
|
agent_badge_idle_fg: Color::Black,
|
|
agent_badge_idle_bg: Color::Cyan,
|
|
operating_chat_fg: Color::Black,
|
|
operating_chat_bg: Color::Blue,
|
|
operating_code_fg: Color::Black,
|
|
operating_code_bg: Color::Magenta,
|
|
}
|
|
}
|
|
|
|
/// Default light theme
|
|
fn default_light() -> Theme {
|
|
Theme {
|
|
name: "default_light".to_string(),
|
|
text: Color::Black,
|
|
background: Color::White,
|
|
focused_panel_border: Color::Rgb(74, 144, 226),
|
|
unfocused_panel_border: Color::Rgb(221, 221, 221),
|
|
focus_beacon_fg: Theme::default_focus_beacon_fg(),
|
|
focus_beacon_bg: Theme::default_focus_beacon_bg(),
|
|
unfocused_beacon_fg: Theme::default_unfocused_beacon_fg(),
|
|
pane_header_active: Theme::default_pane_header_active(),
|
|
pane_header_inactive: Theme::default_pane_header_inactive(),
|
|
pane_hint_text: Theme::default_pane_hint_text(),
|
|
user_message_role: Color::Rgb(0, 85, 164),
|
|
assistant_message_role: Color::Rgb(142, 68, 173),
|
|
tool_output: Color::Gray,
|
|
thinking_panel_title: Color::Rgb(142, 68, 173),
|
|
command_bar_background: Color::White,
|
|
status_background: Color::White,
|
|
mode_normal: Color::Rgb(0, 85, 164),
|
|
mode_editing: Color::Rgb(46, 139, 87),
|
|
mode_model_selection: Color::Rgb(181, 137, 0),
|
|
mode_provider_selection: Color::Rgb(0, 139, 139),
|
|
mode_help: Color::Rgb(142, 68, 173),
|
|
mode_visual: Color::Rgb(142, 68, 173),
|
|
mode_command: Color::Rgb(181, 137, 0),
|
|
selection_bg: Color::Rgb(164, 200, 240),
|
|
selection_fg: Color::Black,
|
|
cursor: Color::Rgb(217, 95, 2),
|
|
code_block_background: Color::Rgb(245, 245, 245),
|
|
code_block_border: Color::Rgb(142, 68, 173),
|
|
code_block_text: Color::Black,
|
|
code_block_keyword: Color::Rgb(181, 137, 0),
|
|
code_block_string: Color::Rgb(46, 139, 87),
|
|
code_block_comment: Color::Gray,
|
|
placeholder: Color::Gray,
|
|
error: Color::Rgb(192, 57, 43),
|
|
info: Color::Green,
|
|
agent_thought: Color::Rgb(0, 85, 164),
|
|
agent_action: Color::Rgb(181, 137, 0),
|
|
agent_action_input: Color::Rgb(0, 139, 139),
|
|
agent_observation: Color::Rgb(46, 139, 87),
|
|
agent_final_answer: Color::Rgb(142, 68, 173),
|
|
agent_badge_running_fg: Color::White,
|
|
agent_badge_running_bg: Color::Rgb(241, 196, 15),
|
|
agent_badge_idle_fg: Color::White,
|
|
agent_badge_idle_bg: Color::Rgb(0, 150, 136),
|
|
operating_chat_fg: Color::White,
|
|
operating_chat_bg: Color::Rgb(0, 85, 164),
|
|
operating_code_fg: Color::White,
|
|
operating_code_bg: Color::Rgb(142, 68, 173),
|
|
}
|
|
}
|
|
|
|
/// Gruvbox theme
|
|
fn gruvbox() -> Theme {
|
|
Theme {
|
|
name: "gruvbox".to_string(),
|
|
text: Color::Rgb(235, 219, 178), // #ebdbb2
|
|
background: Color::Rgb(40, 40, 40), // #282828
|
|
focused_panel_border: Color::Rgb(254, 128, 25), // #fe8019 (orange)
|
|
unfocused_panel_border: Color::Rgb(124, 111, 100), // #7c6f64
|
|
focus_beacon_fg: Theme::default_focus_beacon_fg(),
|
|
focus_beacon_bg: Theme::default_focus_beacon_bg(),
|
|
unfocused_beacon_fg: Theme::default_unfocused_beacon_fg(),
|
|
pane_header_active: Theme::default_pane_header_active(),
|
|
pane_header_inactive: Theme::default_pane_header_inactive(),
|
|
pane_hint_text: Theme::default_pane_hint_text(),
|
|
user_message_role: Color::Rgb(184, 187, 38), // #b8bb26 (green)
|
|
assistant_message_role: Color::Rgb(131, 165, 152), // #83a598 (blue)
|
|
tool_output: Color::Rgb(146, 131, 116),
|
|
thinking_panel_title: Color::Rgb(211, 134, 155), // #d3869b (purple)
|
|
command_bar_background: Color::Rgb(60, 56, 54), // #3c3836
|
|
status_background: Color::Rgb(60, 56, 54),
|
|
mode_normal: Color::Rgb(131, 165, 152), // blue
|
|
mode_editing: Color::Rgb(184, 187, 38), // green
|
|
mode_model_selection: Color::Rgb(250, 189, 47), // yellow
|
|
mode_provider_selection: Color::Rgb(142, 192, 124), // aqua
|
|
mode_help: Color::Rgb(211, 134, 155), // purple
|
|
mode_visual: Color::Rgb(254, 128, 25), // orange
|
|
mode_command: Color::Rgb(250, 189, 47), // yellow
|
|
selection_bg: Color::Rgb(80, 73, 69),
|
|
selection_fg: Color::Rgb(235, 219, 178),
|
|
cursor: Color::Rgb(254, 128, 25),
|
|
code_block_background: Color::Rgb(60, 56, 54),
|
|
code_block_border: Color::Rgb(124, 111, 100),
|
|
code_block_text: Color::Rgb(235, 219, 178),
|
|
code_block_keyword: Color::Rgb(250, 189, 47),
|
|
code_block_string: Color::Rgb(142, 192, 124),
|
|
code_block_comment: Color::Rgb(124, 111, 100),
|
|
placeholder: Color::Rgb(102, 92, 84),
|
|
error: Color::Rgb(251, 73, 52), // #fb4934
|
|
info: Color::Rgb(184, 187, 38),
|
|
agent_thought: Color::Rgb(131, 165, 152),
|
|
agent_action: Color::Rgb(250, 189, 47),
|
|
agent_action_input: Color::Rgb(142, 192, 124),
|
|
agent_observation: Color::Rgb(184, 187, 38),
|
|
agent_final_answer: Color::Rgb(211, 134, 155),
|
|
agent_badge_running_fg: Color::Rgb(40, 40, 40),
|
|
agent_badge_running_bg: Color::Rgb(250, 189, 47),
|
|
agent_badge_idle_fg: Color::Rgb(40, 40, 40),
|
|
agent_badge_idle_bg: Color::Rgb(131, 165, 152),
|
|
operating_chat_fg: Color::Rgb(40, 40, 40),
|
|
operating_chat_bg: Color::Rgb(131, 165, 152),
|
|
operating_code_fg: Color::Rgb(40, 40, 40),
|
|
operating_code_bg: Color::Rgb(211, 134, 155),
|
|
}
|
|
}
|
|
|
|
/// Dracula theme
|
|
fn dracula() -> Theme {
|
|
Theme {
|
|
name: "dracula".to_string(),
|
|
text: Color::Rgb(248, 248, 242), // #f8f8f2
|
|
background: Color::Rgb(40, 42, 54), // #282a36
|
|
focused_panel_border: Color::Rgb(255, 121, 198), // #ff79c6 (pink)
|
|
unfocused_panel_border: Color::Rgb(68, 71, 90), // #44475a
|
|
focus_beacon_fg: Theme::default_focus_beacon_fg(),
|
|
focus_beacon_bg: Theme::default_focus_beacon_bg(),
|
|
unfocused_beacon_fg: Theme::default_unfocused_beacon_fg(),
|
|
pane_header_active: Theme::default_pane_header_active(),
|
|
pane_header_inactive: Theme::default_pane_header_inactive(),
|
|
pane_hint_text: Theme::default_pane_hint_text(),
|
|
user_message_role: Color::Rgb(139, 233, 253), // #8be9fd (cyan)
|
|
assistant_message_role: Color::Rgb(255, 121, 198), // #ff79c6 (pink)
|
|
tool_output: Color::Rgb(98, 114, 164),
|
|
thinking_panel_title: Color::Rgb(189, 147, 249), // #bd93f9 (purple)
|
|
command_bar_background: Color::Rgb(68, 71, 90),
|
|
status_background: Color::Rgb(68, 71, 90),
|
|
mode_normal: Color::Rgb(139, 233, 253),
|
|
mode_editing: Color::Rgb(80, 250, 123), // #50fa7b (green)
|
|
mode_model_selection: Color::Rgb(241, 250, 140), // #f1fa8c (yellow)
|
|
mode_provider_selection: Color::Rgb(139, 233, 253),
|
|
mode_help: Color::Rgb(189, 147, 249),
|
|
mode_visual: Color::Rgb(255, 121, 198),
|
|
mode_command: Color::Rgb(241, 250, 140),
|
|
selection_bg: Color::Rgb(68, 71, 90),
|
|
selection_fg: Color::Rgb(248, 248, 242),
|
|
cursor: Color::Rgb(255, 121, 198),
|
|
code_block_background: Color::Rgb(68, 71, 90),
|
|
code_block_border: Color::Rgb(189, 147, 249),
|
|
code_block_text: Color::Rgb(248, 248, 242),
|
|
code_block_keyword: Color::Rgb(255, 121, 198),
|
|
code_block_string: Color::Rgb(80, 250, 123),
|
|
code_block_comment: Color::Rgb(98, 114, 164),
|
|
placeholder: Color::Rgb(98, 114, 164),
|
|
error: Color::Rgb(255, 85, 85), // #ff5555
|
|
info: Color::Rgb(80, 250, 123),
|
|
agent_thought: Color::Rgb(139, 233, 253),
|
|
agent_action: Color::Rgb(241, 250, 140),
|
|
agent_action_input: Color::Rgb(189, 147, 249),
|
|
agent_observation: Color::Rgb(80, 250, 123),
|
|
agent_final_answer: Color::Rgb(255, 121, 198),
|
|
agent_badge_running_fg: Color::Rgb(40, 42, 54),
|
|
agent_badge_running_bg: Color::Rgb(241, 250, 140),
|
|
agent_badge_idle_fg: Color::Rgb(40, 42, 54),
|
|
agent_badge_idle_bg: Color::Rgb(139, 233, 253),
|
|
operating_chat_fg: Color::Rgb(40, 42, 54),
|
|
operating_chat_bg: Color::Rgb(139, 233, 253),
|
|
operating_code_fg: Color::Rgb(40, 42, 54),
|
|
operating_code_bg: Color::Rgb(189, 147, 249),
|
|
}
|
|
}
|
|
|
|
/// Solarized Dark theme
|
|
fn solarized() -> Theme {
|
|
Theme {
|
|
name: "solarized".to_string(),
|
|
text: Color::Rgb(131, 148, 150), // #839496 (base0)
|
|
background: Color::Rgb(0, 43, 54), // #002b36 (base03)
|
|
focused_panel_border: Color::Rgb(38, 139, 210), // #268bd2 (blue)
|
|
unfocused_panel_border: Color::Rgb(7, 54, 66), // #073642 (base02)
|
|
focus_beacon_fg: Theme::default_focus_beacon_fg(),
|
|
focus_beacon_bg: Theme::default_focus_beacon_bg(),
|
|
unfocused_beacon_fg: Theme::default_unfocused_beacon_fg(),
|
|
pane_header_active: Theme::default_pane_header_active(),
|
|
pane_header_inactive: Theme::default_pane_header_inactive(),
|
|
pane_hint_text: Theme::default_pane_hint_text(),
|
|
user_message_role: Color::Rgb(42, 161, 152), // #2aa198 (cyan)
|
|
assistant_message_role: Color::Rgb(203, 75, 22), // #cb4b16 (orange)
|
|
tool_output: Color::Rgb(101, 123, 131),
|
|
thinking_panel_title: Color::Rgb(108, 113, 196), // #6c71c4 (violet)
|
|
command_bar_background: Color::Rgb(7, 54, 66),
|
|
status_background: Color::Rgb(7, 54, 66),
|
|
mode_normal: Color::Rgb(38, 139, 210), // blue
|
|
mode_editing: Color::Rgb(133, 153, 0), // #859900 (green)
|
|
mode_model_selection: Color::Rgb(181, 137, 0), // #b58900 (yellow)
|
|
mode_provider_selection: Color::Rgb(42, 161, 152), // cyan
|
|
mode_help: Color::Rgb(108, 113, 196), // violet
|
|
mode_visual: Color::Rgb(211, 54, 130), // #d33682 (magenta)
|
|
mode_command: Color::Rgb(181, 137, 0), // yellow
|
|
selection_bg: Color::Rgb(7, 54, 66),
|
|
selection_fg: Color::Rgb(147, 161, 161),
|
|
cursor: Color::Rgb(211, 54, 130),
|
|
code_block_background: Color::Rgb(7, 54, 66),
|
|
code_block_border: Color::Rgb(38, 139, 210),
|
|
code_block_text: Color::Rgb(147, 161, 161),
|
|
code_block_keyword: Color::Rgb(181, 137, 0),
|
|
code_block_string: Color::Rgb(133, 153, 0),
|
|
code_block_comment: Color::Rgb(88, 110, 117),
|
|
placeholder: Color::Rgb(88, 110, 117),
|
|
error: Color::Rgb(220, 50, 47), // #dc322f (red)
|
|
info: Color::Rgb(133, 153, 0),
|
|
agent_thought: Color::Rgb(42, 161, 152),
|
|
agent_action: Color::Rgb(181, 137, 0),
|
|
agent_action_input: Color::Rgb(38, 139, 210),
|
|
agent_observation: Color::Rgb(133, 153, 0),
|
|
agent_final_answer: Color::Rgb(108, 113, 196),
|
|
agent_badge_running_fg: Color::Rgb(0, 43, 54),
|
|
agent_badge_running_bg: Color::Rgb(181, 137, 0),
|
|
agent_badge_idle_fg: Color::Rgb(0, 43, 54),
|
|
agent_badge_idle_bg: Color::Rgb(42, 161, 152),
|
|
operating_chat_fg: Color::Rgb(0, 43, 54),
|
|
operating_chat_bg: Color::Rgb(42, 161, 152),
|
|
operating_code_fg: Color::Rgb(0, 43, 54),
|
|
operating_code_bg: Color::Rgb(108, 113, 196),
|
|
}
|
|
}
|
|
|
|
/// Midnight Ocean theme
|
|
fn midnight_ocean() -> Theme {
|
|
Theme {
|
|
name: "midnight-ocean".to_string(),
|
|
text: Color::Rgb(192, 202, 245),
|
|
background: Color::Rgb(13, 17, 23),
|
|
focused_panel_border: Color::Rgb(88, 166, 255),
|
|
unfocused_panel_border: Color::Rgb(48, 54, 61),
|
|
focus_beacon_fg: Theme::default_focus_beacon_fg(),
|
|
focus_beacon_bg: Theme::default_focus_beacon_bg(),
|
|
unfocused_beacon_fg: Theme::default_unfocused_beacon_fg(),
|
|
pane_header_active: Theme::default_pane_header_active(),
|
|
pane_header_inactive: Theme::default_pane_header_inactive(),
|
|
pane_hint_text: Theme::default_pane_hint_text(),
|
|
user_message_role: Color::Rgb(121, 192, 255),
|
|
assistant_message_role: Color::Rgb(137, 221, 255),
|
|
tool_output: Color::Rgb(84, 110, 122),
|
|
thinking_panel_title: Color::Rgb(158, 206, 106),
|
|
command_bar_background: Color::Rgb(22, 27, 34),
|
|
status_background: Color::Rgb(22, 27, 34),
|
|
mode_normal: Color::Rgb(121, 192, 255),
|
|
mode_editing: Color::Rgb(158, 206, 106),
|
|
mode_model_selection: Color::Rgb(255, 212, 59),
|
|
mode_provider_selection: Color::Rgb(137, 221, 255),
|
|
mode_help: Color::Rgb(255, 115, 157),
|
|
mode_visual: Color::Rgb(246, 140, 245),
|
|
mode_command: Color::Rgb(255, 212, 59),
|
|
selection_bg: Color::Rgb(56, 139, 253),
|
|
selection_fg: Color::Rgb(13, 17, 23),
|
|
cursor: Color::Rgb(246, 140, 245),
|
|
code_block_background: Color::Rgb(22, 27, 34),
|
|
code_block_border: Color::Rgb(88, 166, 255),
|
|
code_block_text: Color::Rgb(192, 202, 245),
|
|
code_block_keyword: Color::Rgb(255, 212, 59),
|
|
code_block_string: Color::Rgb(158, 206, 106),
|
|
code_block_comment: Color::Rgb(110, 118, 129),
|
|
placeholder: Color::Rgb(110, 118, 129),
|
|
error: Color::Rgb(248, 81, 73),
|
|
info: Color::Rgb(158, 206, 106),
|
|
agent_thought: Color::Rgb(121, 192, 255),
|
|
agent_action: Color::Rgb(255, 212, 59),
|
|
agent_action_input: Color::Rgb(137, 221, 255),
|
|
agent_observation: Color::Rgb(158, 206, 106),
|
|
agent_final_answer: Color::Rgb(246, 140, 245),
|
|
agent_badge_running_fg: Color::Rgb(13, 17, 23),
|
|
agent_badge_running_bg: Color::Rgb(255, 212, 59),
|
|
agent_badge_idle_fg: Color::Rgb(13, 17, 23),
|
|
agent_badge_idle_bg: Color::Rgb(137, 221, 255),
|
|
operating_chat_fg: Color::Rgb(13, 17, 23),
|
|
operating_chat_bg: Color::Rgb(121, 192, 255),
|
|
operating_code_fg: Color::Rgb(13, 17, 23),
|
|
operating_code_bg: Color::Rgb(246, 140, 245),
|
|
}
|
|
}
|
|
|
|
/// Rose Pine theme
|
|
fn rose_pine() -> Theme {
|
|
Theme {
|
|
name: "rose-pine".to_string(),
|
|
text: Color::Rgb(224, 222, 244), // #e0def4
|
|
background: Color::Rgb(25, 23, 36), // #191724
|
|
focused_panel_border: Color::Rgb(235, 111, 146), // #eb6f92 (love)
|
|
unfocused_panel_border: Color::Rgb(38, 35, 58), // #26233a
|
|
focus_beacon_fg: Theme::default_focus_beacon_fg(),
|
|
focus_beacon_bg: Theme::default_focus_beacon_bg(),
|
|
unfocused_beacon_fg: Theme::default_unfocused_beacon_fg(),
|
|
pane_header_active: Theme::default_pane_header_active(),
|
|
pane_header_inactive: Theme::default_pane_header_inactive(),
|
|
pane_hint_text: Theme::default_pane_hint_text(),
|
|
user_message_role: Color::Rgb(49, 116, 143), // #31748f (foam)
|
|
assistant_message_role: Color::Rgb(156, 207, 216), // #9ccfd8 (foam light)
|
|
tool_output: Color::Rgb(110, 106, 134),
|
|
thinking_panel_title: Color::Rgb(196, 167, 231), // #c4a7e7 (iris)
|
|
command_bar_background: Color::Rgb(38, 35, 58),
|
|
status_background: Color::Rgb(38, 35, 58),
|
|
mode_normal: Color::Rgb(156, 207, 216),
|
|
mode_editing: Color::Rgb(235, 188, 186), // #ebbcba (rose)
|
|
mode_model_selection: Color::Rgb(246, 193, 119),
|
|
mode_provider_selection: Color::Rgb(49, 116, 143),
|
|
mode_help: Color::Rgb(196, 167, 231),
|
|
mode_visual: Color::Rgb(235, 111, 146),
|
|
mode_command: Color::Rgb(246, 193, 119),
|
|
selection_bg: Color::Rgb(64, 61, 82),
|
|
selection_fg: Color::Rgb(224, 222, 244),
|
|
cursor: Color::Rgb(235, 111, 146),
|
|
code_block_background: Color::Rgb(38, 35, 58),
|
|
code_block_border: Color::Rgb(235, 111, 146),
|
|
code_block_text: Color::Rgb(224, 222, 244),
|
|
code_block_keyword: Color::Rgb(246, 193, 119),
|
|
code_block_string: Color::Rgb(156, 207, 216),
|
|
code_block_comment: Color::Rgb(110, 106, 134),
|
|
placeholder: Color::Rgb(110, 106, 134),
|
|
error: Color::Rgb(235, 111, 146),
|
|
info: Color::Rgb(156, 207, 216),
|
|
agent_thought: Color::Rgb(156, 207, 216),
|
|
agent_action: Color::Rgb(246, 193, 119),
|
|
agent_action_input: Color::Rgb(196, 167, 231),
|
|
agent_observation: Color::Rgb(235, 188, 186),
|
|
agent_final_answer: Color::Rgb(235, 111, 146),
|
|
agent_badge_running_fg: Color::Rgb(25, 23, 36),
|
|
agent_badge_running_bg: Color::Rgb(246, 193, 119),
|
|
agent_badge_idle_fg: Color::Rgb(25, 23, 36),
|
|
agent_badge_idle_bg: Color::Rgb(156, 207, 216),
|
|
operating_chat_fg: Color::Rgb(25, 23, 36),
|
|
operating_chat_bg: Color::Rgb(156, 207, 216),
|
|
operating_code_fg: Color::Rgb(25, 23, 36),
|
|
operating_code_bg: Color::Rgb(196, 167, 231),
|
|
}
|
|
}
|
|
|
|
/// Monokai theme
|
|
fn monokai() -> Theme {
|
|
Theme {
|
|
name: "monokai".to_string(),
|
|
text: Color::Rgb(248, 248, 242), // #f8f8f2
|
|
background: Color::Rgb(39, 40, 34), // #272822
|
|
focused_panel_border: Color::Rgb(249, 38, 114), // #f92672 (pink)
|
|
unfocused_panel_border: Color::Rgb(117, 113, 94), // #75715e
|
|
focus_beacon_fg: Theme::default_focus_beacon_fg(),
|
|
focus_beacon_bg: Theme::default_focus_beacon_bg(),
|
|
unfocused_beacon_fg: Theme::default_unfocused_beacon_fg(),
|
|
pane_header_active: Theme::default_pane_header_active(),
|
|
pane_header_inactive: Theme::default_pane_header_inactive(),
|
|
pane_hint_text: Theme::default_pane_hint_text(),
|
|
user_message_role: Color::Rgb(102, 217, 239), // #66d9ef (cyan)
|
|
assistant_message_role: Color::Rgb(174, 129, 255), // #ae81ff (purple)
|
|
tool_output: Color::Rgb(117, 113, 94),
|
|
thinking_panel_title: Color::Rgb(230, 219, 116), // #e6db74 (yellow)
|
|
command_bar_background: Color::Rgb(39, 40, 34),
|
|
status_background: Color::Rgb(39, 40, 34),
|
|
mode_normal: Color::Rgb(102, 217, 239),
|
|
mode_editing: Color::Rgb(166, 226, 46), // #a6e22e (green)
|
|
mode_model_selection: Color::Rgb(230, 219, 116),
|
|
mode_provider_selection: Color::Rgb(102, 217, 239),
|
|
mode_help: Color::Rgb(174, 129, 255),
|
|
mode_visual: Color::Rgb(249, 38, 114),
|
|
mode_command: Color::Rgb(230, 219, 116),
|
|
selection_bg: Color::Rgb(117, 113, 94),
|
|
selection_fg: Color::Rgb(248, 248, 242),
|
|
cursor: Color::Rgb(249, 38, 114),
|
|
code_block_background: Color::Rgb(50, 51, 46),
|
|
code_block_border: Color::Rgb(249, 38, 114),
|
|
code_block_text: Color::Rgb(248, 248, 242),
|
|
code_block_keyword: Color::Rgb(230, 219, 116),
|
|
code_block_string: Color::Rgb(166, 226, 46),
|
|
code_block_comment: Color::Rgb(117, 113, 94),
|
|
placeholder: Color::Rgb(117, 113, 94),
|
|
error: Color::Rgb(249, 38, 114),
|
|
info: Color::Rgb(166, 226, 46),
|
|
agent_thought: Color::Rgb(102, 217, 239),
|
|
agent_action: Color::Rgb(230, 219, 116),
|
|
agent_action_input: Color::Rgb(174, 129, 255),
|
|
agent_observation: Color::Rgb(166, 226, 46),
|
|
agent_final_answer: Color::Rgb(249, 38, 114),
|
|
agent_badge_running_fg: Color::Rgb(39, 40, 34),
|
|
agent_badge_running_bg: Color::Rgb(230, 219, 116),
|
|
agent_badge_idle_fg: Color::Rgb(39, 40, 34),
|
|
agent_badge_idle_bg: Color::Rgb(102, 217, 239),
|
|
operating_chat_fg: Color::Rgb(39, 40, 34),
|
|
operating_chat_bg: Color::Rgb(102, 217, 239),
|
|
operating_code_fg: Color::Rgb(39, 40, 34),
|
|
operating_code_bg: Color::Rgb(174, 129, 255),
|
|
}
|
|
}
|
|
|
|
/// Material Dark theme
|
|
fn material_dark() -> Theme {
|
|
Theme {
|
|
name: "material-dark".to_string(),
|
|
text: Color::Rgb(238, 255, 255), // #eeffff
|
|
background: Color::Rgb(38, 50, 56), // #263238
|
|
focused_panel_border: Color::Rgb(128, 203, 196), // #80cbc4 (cyan)
|
|
unfocused_panel_border: Color::Rgb(84, 110, 122), // #546e7a
|
|
focus_beacon_fg: Theme::default_focus_beacon_fg(),
|
|
focus_beacon_bg: Theme::default_focus_beacon_bg(),
|
|
unfocused_beacon_fg: Theme::default_unfocused_beacon_fg(),
|
|
pane_header_active: Theme::default_pane_header_active(),
|
|
pane_header_inactive: Theme::default_pane_header_inactive(),
|
|
pane_hint_text: Theme::default_pane_hint_text(),
|
|
user_message_role: Color::Rgb(130, 170, 255), // #82aaff (blue)
|
|
assistant_message_role: Color::Rgb(199, 146, 234), // #c792ea (purple)
|
|
tool_output: Color::Rgb(84, 110, 122),
|
|
thinking_panel_title: Color::Rgb(255, 203, 107), // #ffcb6b (yellow)
|
|
command_bar_background: Color::Rgb(33, 43, 48),
|
|
status_background: Color::Rgb(33, 43, 48),
|
|
mode_normal: Color::Rgb(130, 170, 255),
|
|
mode_editing: Color::Rgb(195, 232, 141), // #c3e88d (green)
|
|
mode_model_selection: Color::Rgb(255, 203, 107),
|
|
mode_provider_selection: Color::Rgb(128, 203, 196),
|
|
mode_help: Color::Rgb(199, 146, 234),
|
|
mode_visual: Color::Rgb(240, 113, 120), // #f07178 (red)
|
|
mode_command: Color::Rgb(255, 203, 107),
|
|
selection_bg: Color::Rgb(84, 110, 122),
|
|
selection_fg: Color::Rgb(238, 255, 255),
|
|
cursor: Color::Rgb(255, 204, 0),
|
|
code_block_background: Color::Rgb(33, 43, 48),
|
|
code_block_border: Color::Rgb(128, 203, 196),
|
|
code_block_text: Color::Rgb(238, 255, 255),
|
|
code_block_keyword: Color::Rgb(255, 203, 107),
|
|
code_block_string: Color::Rgb(195, 232, 141),
|
|
code_block_comment: Color::Rgb(84, 110, 122),
|
|
placeholder: Color::Rgb(84, 110, 122),
|
|
error: Color::Rgb(240, 113, 120),
|
|
info: Color::Rgb(195, 232, 141),
|
|
agent_thought: Color::Rgb(128, 203, 196),
|
|
agent_action: Color::Rgb(255, 203, 107),
|
|
agent_action_input: Color::Rgb(199, 146, 234),
|
|
agent_observation: Color::Rgb(195, 232, 141),
|
|
agent_final_answer: Color::Rgb(240, 113, 120),
|
|
agent_badge_running_fg: Color::Rgb(38, 50, 56),
|
|
agent_badge_running_bg: Color::Rgb(255, 203, 107),
|
|
agent_badge_idle_fg: Color::Rgb(38, 50, 56),
|
|
agent_badge_idle_bg: Color::Rgb(128, 203, 196),
|
|
operating_chat_fg: Color::Rgb(38, 50, 56),
|
|
operating_chat_bg: Color::Rgb(130, 170, 255),
|
|
operating_code_fg: Color::Rgb(38, 50, 56),
|
|
operating_code_bg: Color::Rgb(199, 146, 234),
|
|
}
|
|
}
|
|
|
|
/// Material Light theme
|
|
fn material_light() -> Theme {
|
|
Theme {
|
|
name: "material-light".to_string(),
|
|
text: Color::Rgb(33, 33, 33),
|
|
background: Color::Rgb(236, 239, 241),
|
|
focused_panel_border: Color::Rgb(0, 150, 136),
|
|
unfocused_panel_border: Color::Rgb(176, 190, 197),
|
|
focus_beacon_fg: Theme::default_focus_beacon_fg(),
|
|
focus_beacon_bg: Theme::default_focus_beacon_bg(),
|
|
unfocused_beacon_fg: Theme::default_unfocused_beacon_fg(),
|
|
pane_header_active: Theme::default_pane_header_active(),
|
|
pane_header_inactive: Theme::default_pane_header_inactive(),
|
|
pane_hint_text: Theme::default_pane_hint_text(),
|
|
user_message_role: Color::Rgb(68, 138, 255),
|
|
assistant_message_role: Color::Rgb(124, 77, 255),
|
|
tool_output: Color::Rgb(144, 164, 174),
|
|
thinking_panel_title: Color::Rgb(245, 124, 0),
|
|
command_bar_background: Color::Rgb(255, 255, 255),
|
|
status_background: Color::Rgb(255, 255, 255),
|
|
mode_normal: Color::Rgb(68, 138, 255),
|
|
mode_editing: Color::Rgb(56, 142, 60),
|
|
mode_model_selection: Color::Rgb(245, 124, 0),
|
|
mode_provider_selection: Color::Rgb(0, 150, 136),
|
|
mode_help: Color::Rgb(124, 77, 255),
|
|
mode_visual: Color::Rgb(211, 47, 47),
|
|
mode_command: Color::Rgb(245, 124, 0),
|
|
selection_bg: Color::Rgb(176, 190, 197),
|
|
selection_fg: Color::Rgb(33, 33, 33),
|
|
cursor: Color::Rgb(194, 24, 91),
|
|
code_block_background: Color::Rgb(248, 249, 250),
|
|
code_block_border: Color::Rgb(0, 150, 136),
|
|
code_block_text: Color::Rgb(33, 33, 33),
|
|
code_block_keyword: Color::Rgb(245, 124, 0),
|
|
code_block_string: Color::Rgb(56, 142, 60),
|
|
code_block_comment: Color::Rgb(144, 164, 174),
|
|
placeholder: Color::Rgb(144, 164, 174),
|
|
error: Color::Rgb(211, 47, 47),
|
|
info: Color::Rgb(56, 142, 60),
|
|
agent_thought: Color::Rgb(68, 138, 255),
|
|
agent_action: Color::Rgb(245, 124, 0),
|
|
agent_action_input: Color::Rgb(124, 77, 255),
|
|
agent_observation: Color::Rgb(56, 142, 60),
|
|
agent_final_answer: Color::Rgb(211, 47, 47),
|
|
agent_badge_running_fg: Color::White,
|
|
agent_badge_running_bg: Color::Rgb(245, 124, 0),
|
|
agent_badge_idle_fg: Color::White,
|
|
agent_badge_idle_bg: Color::Rgb(0, 150, 136),
|
|
operating_chat_fg: Color::White,
|
|
operating_chat_bg: Color::Rgb(68, 138, 255),
|
|
operating_code_fg: Color::White,
|
|
operating_code_bg: Color::Rgb(124, 77, 255),
|
|
}
|
|
}
|
|
|
|
/// Grayscale high-contrast theme
|
|
fn grayscale_high_contrast() -> Theme {
|
|
Theme {
|
|
name: "grayscale_high_contrast".to_string(),
|
|
text: Color::Rgb(247, 247, 247),
|
|
background: Color::Black,
|
|
focused_panel_border: Color::White,
|
|
unfocused_panel_border: Color::Rgb(76, 76, 76),
|
|
focus_beacon_fg: Theme::default_focus_beacon_fg(),
|
|
focus_beacon_bg: Theme::default_focus_beacon_bg(),
|
|
unfocused_beacon_fg: Theme::default_unfocused_beacon_fg(),
|
|
pane_header_active: Theme::default_pane_header_active(),
|
|
pane_header_inactive: Theme::default_pane_header_inactive(),
|
|
pane_hint_text: Theme::default_pane_hint_text(),
|
|
user_message_role: Color::Rgb(240, 240, 240),
|
|
assistant_message_role: Color::Rgb(214, 214, 214),
|
|
tool_output: Color::Rgb(189, 189, 189),
|
|
thinking_panel_title: Color::Rgb(224, 224, 224),
|
|
command_bar_background: Color::Black,
|
|
status_background: Color::Rgb(15, 15, 15),
|
|
mode_normal: Color::White,
|
|
mode_editing: Color::Rgb(230, 230, 230),
|
|
mode_model_selection: Color::Rgb(204, 204, 204),
|
|
mode_provider_selection: Color::Rgb(179, 179, 179),
|
|
mode_help: Color::Rgb(153, 153, 153),
|
|
mode_visual: Color::Rgb(242, 242, 242),
|
|
mode_command: Color::Rgb(208, 208, 208),
|
|
selection_bg: Color::Rgb(240, 240, 240),
|
|
selection_fg: Color::Black,
|
|
cursor: Color::White,
|
|
code_block_background: Color::Rgb(15, 15, 15),
|
|
code_block_border: Color::White,
|
|
code_block_text: Color::Rgb(247, 247, 247),
|
|
code_block_keyword: Color::Rgb(204, 204, 204),
|
|
code_block_string: Color::Rgb(214, 214, 214),
|
|
code_block_comment: Color::Rgb(122, 122, 122),
|
|
placeholder: Color::Rgb(122, 122, 122),
|
|
error: Color::White,
|
|
info: Color::Rgb(200, 200, 200),
|
|
agent_thought: Color::Rgb(230, 230, 230),
|
|
agent_action: Color::Rgb(204, 204, 204),
|
|
agent_action_input: Color::Rgb(176, 176, 176),
|
|
agent_observation: Color::Rgb(153, 153, 153),
|
|
agent_final_answer: Color::White,
|
|
agent_badge_running_fg: Color::Black,
|
|
agent_badge_running_bg: Color::Rgb(247, 247, 247),
|
|
agent_badge_idle_fg: Color::Black,
|
|
agent_badge_idle_bg: Color::Rgb(189, 189, 189),
|
|
operating_chat_fg: Color::Black,
|
|
operating_chat_bg: Color::Rgb(242, 242, 242),
|
|
operating_code_fg: Color::Black,
|
|
operating_code_bg: Color::Rgb(191, 191, 191),
|
|
}
|
|
}
|
|
|
|
// Helper functions for color serialization/deserialization
|
|
|
|
fn deserialize_color<'de, D>(deserializer: D) -> Result<Color, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
let s = String::deserialize(deserializer)?;
|
|
parse_color(&s).map_err(serde::de::Error::custom)
|
|
}
|
|
|
|
fn serialize_color<S>(color: &Color, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: serde::Serializer,
|
|
{
|
|
let s = color_to_string(color);
|
|
serializer.serialize_str(&s)
|
|
}
|
|
|
|
fn parse_color(s: &str) -> Result<Color, String> {
|
|
if let Some(hex) = s.strip_prefix('#')
|
|
&& hex.len() == 6
|
|
{
|
|
let r =
|
|
u8::from_str_radix(&hex[0..2], 16).map_err(|_| format!("Invalid hex color: {}", s))?;
|
|
let g =
|
|
u8::from_str_radix(&hex[2..4], 16).map_err(|_| format!("Invalid hex color: {}", s))?;
|
|
let b =
|
|
u8::from_str_radix(&hex[4..6], 16).map_err(|_| format!("Invalid hex color: {}", s))?;
|
|
return Ok(Color::Rgb(r, g, b));
|
|
}
|
|
|
|
// Try named colors
|
|
match s.to_lowercase().as_str() {
|
|
"black" => Ok(Color::Black),
|
|
"red" => Ok(Color::Red),
|
|
"green" => Ok(Color::Green),
|
|
"yellow" => Ok(Color::Yellow),
|
|
"blue" => Ok(Color::Blue),
|
|
"magenta" => Ok(Color::Magenta),
|
|
"cyan" => Ok(Color::Cyan),
|
|
"gray" | "grey" => Ok(Color::Gray),
|
|
"darkgray" | "darkgrey" => Ok(Color::DarkGray),
|
|
"lightred" => Ok(Color::LightRed),
|
|
"lightgreen" => Ok(Color::LightGreen),
|
|
"lightyellow" => Ok(Color::LightYellow),
|
|
"lightblue" => Ok(Color::LightBlue),
|
|
"lightmagenta" => Ok(Color::LightMagenta),
|
|
"lightcyan" => Ok(Color::LightCyan),
|
|
"white" => Ok(Color::White),
|
|
_ => Err(format!("Unknown color: {}", s)),
|
|
}
|
|
}
|
|
|
|
fn color_to_string(color: &Color) -> String {
|
|
match color {
|
|
Color::Black => "black".to_string(),
|
|
Color::Red => "red".to_string(),
|
|
Color::Green => "green".to_string(),
|
|
Color::Yellow => "yellow".to_string(),
|
|
Color::Blue => "blue".to_string(),
|
|
Color::Magenta => "magenta".to_string(),
|
|
Color::Cyan => "cyan".to_string(),
|
|
Color::Gray => "gray".to_string(),
|
|
Color::DarkGray => "darkgray".to_string(),
|
|
Color::LightRed => "lightred".to_string(),
|
|
Color::LightGreen => "lightgreen".to_string(),
|
|
Color::LightYellow => "lightyellow".to_string(),
|
|
Color::LightBlue => "lightblue".to_string(),
|
|
Color::LightMagenta => "lightmagenta".to_string(),
|
|
Color::LightCyan => "lightcyan".to_string(),
|
|
Color::White => "white".to_string(),
|
|
Color::Rgb(r, g, b) => format!("#{:02x}{:02x}{:02x}", r, g, b),
|
|
_ => "#ffffff".to_string(),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_color_parsing() {
|
|
assert!(matches!(parse_color("#ff0000"), Ok(Color::Rgb(255, 0, 0))));
|
|
assert!(matches!(parse_color("red"), Ok(Color::Red)));
|
|
assert!(matches!(parse_color("lightblue"), Ok(Color::LightBlue)));
|
|
}
|
|
|
|
#[test]
|
|
fn test_built_in_themes() {
|
|
let themes = built_in_themes();
|
|
assert!(themes.contains_key("default_dark"));
|
|
assert!(themes.contains_key("gruvbox"));
|
|
assert!(themes.contains_key("dracula"));
|
|
assert!(themes.contains_key("grayscale-high-contrast"));
|
|
}
|
|
}
|