Skip to main content

vtcode_commons/
styling.rs

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