use anstyle::{Color as AnsiColorEnum, Style as AnsiStyle};
use ratatui::style::{Color, Modifier, Style};
use unicode_width::UnicodeWidthStr;
use crate::tui::ui::theme;
pub use vtcode_commons::ui_protocol::{convert_style, theme_from_color_fields};
use super::types::{InlineTextStyle, InlineTheme};
pub fn theme_from_styles(styles: &theme::ThemeStyles) -> InlineTheme {
theme_from_color_fields(
styles.foreground,
styles.background,
styles.primary,
styles.secondary,
styles.tool,
styles.tool_detail,
styles.pty_output,
)
}
pub fn measure_text_width(text: &str) -> u16 {
UnicodeWidthStr::width(text) as u16
}
pub fn ratatui_color_from_ansi(color: AnsiColorEnum) -> Color {
crate::design::color::anstyle_to_ratatui_color(color)
}
pub use crate::design::color::hex_to_ratatui_color;
pub fn agent_color_style(color: Option<&str>, fallback_color: Color) -> Style {
let light = matches!(
vtcode_commons::ansi_capabilities::detect_color_scheme(),
vtcode_commons::ansi_capabilities::ColorScheme::Light
);
let color = color
.map(|c| crate::design::color::resolve_agent_color(c, fallback_color, light))
.unwrap_or(fallback_color);
Style::default().fg(color).add_modifier(Modifier::BOLD)
}
pub fn ratatui_style_from_inline(
style: &InlineTextStyle,
fallback: Option<AnsiColorEnum>,
) -> Style {
crate::design::style::inline_text_style_to_ratatui(
style.color,
style.bg_color,
style.effects,
fallback,
)
}
pub fn ratatui_pty_style_from_inline(
style: &InlineTextStyle,
fallback: Option<AnsiColorEnum>,
) -> Style {
ratatui_style_from_inline(style, fallback)
.remove_modifier(Modifier::BOLD)
.add_modifier(Modifier::DIM)
}
pub fn ratatui_style_from_ansi(style: AnsiStyle) -> Style {
crate::design::style::anstyle_to_ratatui_style(style)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::design::color::resolve_agent_color;
use vtcode_config::constants::ui::{
AGENT_COLOR_AUTO, AGENT_COLOR_BUILD, AGENT_COLOR_DUCK, AGENT_COLOR_PLAN,
};
#[test]
fn agent_color_style_applies_mode_color_with_bold() {
let fallback = Color::LightMagenta;
let style = agent_color_style(Some(AGENT_COLOR_BUILD), fallback);
assert_ne!(style.fg, Some(fallback));
assert!(style.add_modifier.contains(Modifier::BOLD));
for hue in [
AGENT_COLOR_BUILD,
AGENT_COLOR_AUTO,
AGENT_COLOR_PLAN,
AGENT_COLOR_DUCK,
] {
let s = agent_color_style(Some(hue), fallback);
assert!(s.add_modifier.contains(Modifier::BOLD));
assert!(s.fg.is_some());
}
}
#[test]
fn agent_color_style_is_theme_aware_and_distinct_per_mode() {
let fallback = Color::LightMagenta;
let dark = [
resolve_agent_color(AGENT_COLOR_BUILD, fallback, false),
resolve_agent_color(AGENT_COLOR_AUTO, fallback, false),
resolve_agent_color(AGENT_COLOR_PLAN, fallback, false),
resolve_agent_color(AGENT_COLOR_DUCK, fallback, false),
];
assert_eq!(
dark,
[
Color::LightRed,
Color::LightGreen,
Color::LightBlue,
Color::LightMagenta
]
);
let light = [
resolve_agent_color(AGENT_COLOR_BUILD, fallback, true),
resolve_agent_color(AGENT_COLOR_AUTO, fallback, true),
resolve_agent_color(AGENT_COLOR_PLAN, fallback, true),
resolve_agent_color(AGENT_COLOR_DUCK, fallback, true),
];
assert_eq!(
light,
[Color::Red, Color::Green, Color::Blue, Color::Magenta]
);
assert_eq!(
dark.iter().collect::<std::collections::HashSet<_>>().len(),
4
);
assert_eq!(
light.iter().collect::<std::collections::HashSet<_>>().len(),
4
);
}
#[test]
fn agent_color_style_accepts_raw_hue_names_and_hex() {
let fallback = Color::LightMagenta;
assert_eq!(
resolve_agent_color("green", fallback, false),
Color::LightGreen
);
assert_eq!(resolve_agent_color("blue", fallback, true), Color::Blue);
assert_eq!(
resolve_agent_color("#FF0000", fallback, false),
Color::Rgb(255, 0, 0)
);
}
#[test]
fn agent_color_style_falls_back_when_missing_or_invalid() {
let fallback = Color::LightMagenta;
let missing = agent_color_style(None, fallback);
assert_eq!(missing.fg, Some(fallback));
assert!(missing.add_modifier.contains(Modifier::BOLD));
let invalid = agent_color_style(Some("not-a-color"), fallback);
assert_eq!(invalid.fg, Some(fallback));
}
}