vtcode_ui/tui/core_tui/
style.rs1use anstyle::{Color as AnsiColorEnum, Style as AnsiStyle};
2use ratatui::style::{Color, Modifier, Style};
3use unicode_width::UnicodeWidthStr;
4
5use crate::tui::ui::theme;
6
7pub use vtcode_commons::ui_protocol::{convert_style, theme_from_color_fields};
9
10use super::types::{InlineTextStyle, InlineTheme};
11
12pub fn theme_from_styles(styles: &theme::ThemeStyles) -> InlineTheme {
13 theme_from_color_fields(
14 styles.foreground,
15 styles.background,
16 styles.primary,
17 styles.secondary,
18 styles.tool,
19 styles.tool_detail,
20 styles.pty_output,
21 styles.error,
22 styles.warning,
23 )
24}
25
26pub(crate) fn measure_text_width(text: &str) -> u16 {
27 UnicodeWidthStr::width(text) as u16
28}
29
30pub(crate) fn ratatui_color_from_ansi(color: AnsiColorEnum) -> Color {
35 crate::design::color::anstyle_to_ratatui_color(color)
36}
37
38pub(crate) use crate::design::color::hex_to_ratatui_color;
41
42pub(crate) fn agent_color_style(color: Option<&str>, fallback_color: Color) -> Style {
49 let light = matches!(
50 vtcode_commons::ansi_capabilities::detect_color_scheme(),
51 vtcode_commons::ansi_capabilities::ColorScheme::Light
52 );
53 let color = color
54 .map(|c| crate::design::color::resolve_agent_color(c, fallback_color, light))
55 .unwrap_or(fallback_color);
56 Style::default().fg(color).add_modifier(Modifier::BOLD)
57}
58
59pub(crate) fn ratatui_style_from_inline(style: &InlineTextStyle, fallback: Option<AnsiColorEnum>) -> Style {
60 crate::design::style::inline_text_style_to_ratatui(style.color, style.bg_color, style.effects, fallback)
61}
62
63pub(crate) fn ratatui_pty_style_from_inline(style: &InlineTextStyle, fallback: Option<AnsiColorEnum>) -> Style {
65 ratatui_style_from_inline(style, fallback).remove_modifier(Modifier::BOLD)
66}
67
68pub(crate) fn ratatui_style_from_ansi(style: AnsiStyle) -> Style {
70 crate::design::style::anstyle_to_ratatui_style(style)
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76 use crate::design::color::resolve_agent_color;
77 use vtcode_config::constants::ui::{AGENT_COLOR_AUTO, AGENT_COLOR_BUILD, AGENT_COLOR_DUCK, AGENT_COLOR_PLAN};
78
79 #[test]
80 fn agent_color_style_applies_mode_color_with_bold() {
81 let fallback = Color::LightMagenta;
82 let style = agent_color_style(Some(AGENT_COLOR_BUILD), fallback);
83 assert_ne!(style.fg, Some(fallback));
86 assert!(style.add_modifier.contains(Modifier::BOLD));
87
88 for hue in [AGENT_COLOR_BUILD, AGENT_COLOR_AUTO, AGENT_COLOR_PLAN, AGENT_COLOR_DUCK] {
89 let s = agent_color_style(Some(hue), fallback);
90 assert!(s.add_modifier.contains(Modifier::BOLD));
91 assert!(s.fg.is_some());
92 }
93 }
94
95 #[test]
96 fn agent_color_style_is_theme_aware_and_distinct_per_mode() {
97 let fallback = Color::LightMagenta;
98 let build = Color::Rgb(0x73, 0xA1, 0x8E);
100 let dark = [
102 resolve_agent_color(AGENT_COLOR_BUILD, fallback, false),
103 resolve_agent_color(AGENT_COLOR_AUTO, fallback, false),
104 resolve_agent_color(AGENT_COLOR_PLAN, fallback, false),
105 resolve_agent_color(AGENT_COLOR_DUCK, fallback, false),
106 ];
107 assert_eq!(dark, [build, Color::LightGreen, Color::LightBlue, Color::LightMagenta]);
108 let light = [
110 resolve_agent_color(AGENT_COLOR_BUILD, fallback, true),
111 resolve_agent_color(AGENT_COLOR_AUTO, fallback, true),
112 resolve_agent_color(AGENT_COLOR_PLAN, fallback, true),
113 resolve_agent_color(AGENT_COLOR_DUCK, fallback, true),
114 ];
115 assert_eq!(light, [build, Color::Green, Color::Blue, Color::Magenta]);
116 assert_eq!(dark.iter().collect::<std::collections::HashSet<_>>().len(), 4);
118 assert_eq!(light.iter().collect::<std::collections::HashSet<_>>().len(), 4);
119 }
120
121 #[test]
122 fn agent_color_style_accepts_raw_hue_names_and_hex() {
123 let fallback = Color::LightMagenta;
124 assert_eq!(resolve_agent_color("green", fallback, false), Color::LightGreen);
126 assert_eq!(resolve_agent_color("blue", fallback, true), Color::Blue);
127 assert_eq!(resolve_agent_color("#FF0000", fallback, false), Color::Rgb(255, 0, 0));
129 }
130
131 #[test]
132 fn agent_color_style_falls_back_when_missing_or_invalid() {
133 let fallback = Color::LightMagenta;
134 let missing = agent_color_style(None, fallback);
135 assert_eq!(missing.fg, Some(fallback));
136 assert!(missing.add_modifier.contains(Modifier::BOLD));
137
138 let invalid = agent_color_style(Some("not-a-color"), fallback);
139 assert_eq!(invalid.fg, Some(fallback));
140 }
141}