Skip to main content

vtcode_commons/
styling.rs

1//! Unified message styles and their logical mappings
2
3use crate::color_policy;
4use anstyle::{AnsiColor, Color, Effects, RgbColor, Style};
5
6/// Standard color palette with semantic names
7#[derive(Debug, Clone, Copy)]
8pub struct ColorPalette {
9    pub success: Color, // Green
10    pub error: Color,   // Red
11    pub warning: Color, // Red
12    pub info: Color,    // Cyan
13    pub accent: Color,  // Magenta
14    pub primary: Color, // Cyan
15    pub muted: Color,   // Gray/Dim
16}
17
18impl Default for ColorPalette {
19    fn default() -> Self {
20        Self {
21            success: Color::Ansi(AnsiColor::Green),
22            error: Color::Ansi(AnsiColor::Red),
23            warning: Color::Ansi(AnsiColor::Red),
24            info: Color::Ansi(AnsiColor::Cyan),
25            accent: Color::Ansi(AnsiColor::Magenta),
26            primary: Color::Ansi(AnsiColor::Cyan),
27            muted: Color::Ansi(AnsiColor::BrightBlack),
28        }
29    }
30}
31
32/// Render text with a single color and optional effects
33pub fn render_styled(text: &str, color: Color, effects: Option<String>) -> String {
34    let mut style = Style::new();
35    if color_policy::color_output_enabled() {
36        style = style.fg_color(Some(color));
37    }
38
39    if let Some(effects_str) = effects {
40        let mut ansi_effects = Effects::new();
41
42        for effect in effects_str.split(',') {
43            let effect = effect.trim().to_lowercase();
44            match effect.as_str() {
45                "bold" => ansi_effects |= Effects::BOLD,
46                "dim" | "dimmed" => ansi_effects |= Effects::DIMMED,
47                "italic" => ansi_effects |= Effects::ITALIC,
48                "underline" => ansi_effects |= Effects::UNDERLINE,
49                "blink" => ansi_effects |= Effects::BLINK,
50                "invert" | "reversed" => ansi_effects |= Effects::INVERT,
51                "hidden" => ansi_effects |= Effects::HIDDEN,
52                "strikethrough" => ansi_effects |= Effects::STRIKETHROUGH,
53                _ => {}
54            }
55        }
56
57        style = style.effects(ansi_effects);
58    }
59
60    let prefix = style.to_string();
61    if prefix.is_empty() {
62        text.to_string()
63    } else {
64        format!("{}{}{}", prefix, text, "\x1b[0m")
65    }
66}
67
68/// Build style from CSS/terminal color name
69pub fn style_from_color_name(name: &str) -> Style {
70    let (color_name, dimmed) = if let Some(idx) = name.find(':') {
71        let (color, modifier) = name.split_at(idx);
72        (color, modifier.strip_prefix(':').unwrap_or(""))
73    } else {
74        (name, "")
75    };
76
77    let color = match color_name.to_lowercase().as_str() {
78        "red" => Color::Ansi(AnsiColor::Red),
79        "green" => Color::Ansi(AnsiColor::Green),
80        "blue" => Color::Ansi(AnsiColor::Blue),
81        "yellow" => Color::Ansi(AnsiColor::Yellow),
82        "cyan" => Color::Ansi(AnsiColor::Cyan),
83        "magenta" | "purple" => Color::Ansi(AnsiColor::Magenta),
84        "white" => Color::Ansi(AnsiColor::White),
85        "black" => Color::Ansi(AnsiColor::Black),
86        _ => return Style::new(),
87    };
88
89    let mut style = Style::new().fg_color(Some(color));
90    if dimmed.eq_ignore_ascii_case("dimmed") {
91        style = style.dimmed();
92    }
93    style
94}
95
96/// Create a bold colored style from AnsiColor
97pub fn bold_color(color: AnsiColor) -> Style {
98    Style::new().bold().fg_color(Some(Color::Ansi(color)))
99}
100
101/// Create a dimmed colored style from AnsiColor
102pub fn dimmed_color(color: AnsiColor) -> Style {
103    Style::new().dimmed().fg_color(Some(Color::Ansi(color)))
104}
105
106/// Diff color palette for consistent git diff styling
107/// Uses standard ANSI colors without bold for accessibility and consistency.
108#[derive(Debug, Clone, Copy)]
109pub struct DiffColorPalette {
110    pub added_fg: Color,
111    pub added_bg: Color,
112    pub removed_fg: Color,
113    pub removed_bg: Color,
114    pub header_fg: Color,
115    pub header_bg: Color,
116}
117
118impl Default for DiffColorPalette {
119    fn default() -> Self {
120        Self {
121            added_fg: Color::Ansi(AnsiColor::Green),
122            added_bg: Color::Rgb(RgbColor(10, 24, 10)),
123            removed_fg: Color::Ansi(AnsiColor::Red),
124            removed_bg: Color::Rgb(RgbColor(24, 10, 10)),
125            header_fg: Color::Ansi(AnsiColor::Cyan),
126            header_bg: Color::Rgb(RgbColor(10, 16, 20)),
127        }
128    }
129}
130
131impl DiffColorPalette {
132    pub fn added_style(&self) -> Style {
133        Style::new().fg_color(Some(self.added_fg))
134    }
135
136    pub fn removed_style(&self) -> Style {
137        Style::new().fg_color(Some(self.removed_fg))
138    }
139
140    pub fn header_style(&self) -> Style {
141        Style::new().fg_color(Some(self.header_fg))
142    }
143}
144
145// Re-export diff theme configuration
146pub use crate::diff_theme::{
147    DiffColorLevel, DiffTheme, diff_add_bg, diff_del_bg, diff_gutter_bg_add_light,
148    diff_gutter_bg_del_light, diff_gutter_fg_light,
149};