Skip to main content

vtcode_commons/
styling.rs

1//! Unified message styles and their logical mappings
2
3use crate::ansi_codes::RESET;
4use crate::color_policy;
5use anstyle::{AnsiColor, Color, Effects, Style};
6
7/// Standard color palette with semantic names
8#[derive(Debug, Clone, Copy)]
9pub struct ColorPalette {
10    pub success: Color, // Green
11    pub error: Color,   // Red
12    pub warning: Color, // Red
13    pub info: Color,    // Cyan
14    pub accent: Color,  // Magenta
15    pub primary: Color, // Cyan
16    pub muted: Color,   // Gray/Dim
17}
18
19impl Default for ColorPalette {
20    fn default() -> Self {
21        Self {
22            success: Color::Ansi(AnsiColor::Green),
23            error: Color::Ansi(AnsiColor::Red),
24            warning: Color::Ansi(AnsiColor::Red),
25            info: Color::Ansi(AnsiColor::Cyan),
26            accent: Color::Ansi(AnsiColor::Magenta),
27            primary: Color::Ansi(AnsiColor::Cyan),
28            muted: Color::Ansi(AnsiColor::BrightBlack),
29        }
30    }
31}
32
33/// Render text with a single color and optional effects
34pub fn render_styled(text: &str, color: Color, effects: Option<String>) -> String {
35    let mut style = Style::new();
36    if color_policy::color_output_enabled() {
37        style = style.fg_color(Some(color));
38    }
39
40    if let Some(effects_str) = effects {
41        let mut ansi_effects = Effects::new();
42
43        for effect in effects_str.split(',') {
44            let effect = effect.trim().to_lowercase();
45            match effect.as_str() {
46                "bold" => ansi_effects |= Effects::BOLD,
47                "dim" | "dimmed" => ansi_effects |= Effects::DIMMED,
48                "italic" => ansi_effects |= Effects::ITALIC,
49                "underline" => ansi_effects |= Effects::UNDERLINE,
50                "blink" => ansi_effects |= Effects::BLINK,
51                "invert" | "reversed" => ansi_effects |= Effects::INVERT,
52                "hidden" => ansi_effects |= Effects::HIDDEN,
53                "strikethrough" => ansi_effects |= Effects::STRIKETHROUGH,
54                _ => {}
55            }
56        }
57
58        style = style.effects(ansi_effects);
59    }
60
61    let prefix = style.to_string();
62    if prefix.is_empty() {
63        text.to_string()
64    } else {
65        format!("{prefix}{text}{RESET}")
66    }
67}
68
69/// Build style from CSS/terminal color name
70pub fn style_from_color_name(name: &str) -> Style {
71    let (color_name, dimmed) = if let Some(idx) = name.find(':') {
72        let (color, modifier) = name.split_at(idx);
73        (color, modifier.strip_prefix(':').unwrap_or(""))
74    } else {
75        (name, "")
76    };
77
78    let color = match color_name.to_lowercase().as_str() {
79        "red" => Color::Ansi(AnsiColor::Red),
80        "green" => Color::Ansi(AnsiColor::Green),
81        "blue" => Color::Ansi(AnsiColor::Blue),
82        "yellow" => Color::Ansi(AnsiColor::Yellow),
83        "cyan" => Color::Ansi(AnsiColor::Cyan),
84        "magenta" | "purple" => Color::Ansi(AnsiColor::Magenta),
85        "white" => Color::Ansi(AnsiColor::White),
86        "black" => Color::Ansi(AnsiColor::Black),
87        _ => return Style::new(),
88    };
89
90    let mut style = Style::new().fg_color(Some(color));
91    if dimmed.eq_ignore_ascii_case("dimmed") {
92        style = style.dimmed();
93    }
94    style
95}
96
97/// Create a bold colored style from AnsiColor
98pub fn bold_color(color: AnsiColor) -> Style {
99    Style::new().bold().fg_color(Some(Color::Ansi(color)))
100}
101
102/// Create a dimmed colored style from AnsiColor
103pub fn dimmed_color(color: AnsiColor) -> Style {
104    Style::new().dimmed().fg_color(Some(Color::Ansi(color)))
105}
106
107/// Diff color palette for consistent git diff styling
108/// Uses standard ANSI colors without bold for accessibility and consistency.
109#[derive(Debug, Clone, Copy)]
110pub struct DiffColorPalette {
111    pub added_fg: Color,
112    pub added_bg: Color,
113    pub removed_fg: Color,
114    pub removed_bg: Color,
115    pub header_fg: Color,
116    pub header_bg: Color,
117}
118
119impl Default for DiffColorPalette {
120    fn default() -> Self {
121        Self {
122            added_fg: Color::Ansi(AnsiColor::Green),
123            added_bg: Color::Ansi(AnsiColor::Green),
124            removed_fg: Color::Ansi(AnsiColor::Red),
125            removed_bg: Color::Ansi(AnsiColor::Red),
126            header_fg: Color::Ansi(AnsiColor::Cyan),
127            header_bg: Color::Ansi(AnsiColor::Cyan),
128        }
129    }
130}
131
132impl DiffColorPalette {
133    pub fn added_style(&self) -> Style {
134        Style::new().fg_color(Some(self.added_fg))
135    }
136
137    pub fn removed_style(&self) -> Style {
138        Style::new().fg_color(Some(self.removed_fg))
139    }
140
141    pub fn header_style(&self) -> Style {
142        Style::new().fg_color(Some(self.header_fg))
143    }
144}
145
146// Re-export diff theme configuration
147pub use crate::diff_theme::{
148    DiffColorLevel, DiffTheme, diff_add_bg, diff_del_bg, diff_gutter_bg_add_light,
149    diff_gutter_bg_del_light, diff_gutter_fg_light,
150};
151
152/// Style presets for consistent UI theming.
153///
154/// Provides convenience constructors for common ANSI styles used across
155/// VT Code's terminal output.
156pub struct Styles;
157
158impl Styles {
159    /// Error message style (red)
160    pub fn error() -> Style {
161        Style::new().fg_color(Some(Color::Ansi(AnsiColor::Red)))
162    }
163
164    /// Warning message style (red)
165    pub fn warning() -> Style {
166        Style::new().fg_color(Some(Color::Ansi(AnsiColor::Red)))
167    }
168
169    /// Success message style (green)
170    pub fn success() -> Style {
171        Style::new().fg_color(Some(Color::Ansi(AnsiColor::Green)))
172    }
173
174    /// Info message style (cyan)
175    pub fn info() -> Style {
176        Style::new().fg_color(Some(Color::Ansi(AnsiColor::Cyan)))
177    }
178
179    /// Debug message style (cyan, dimmed)
180    pub fn debug() -> Style {
181        Style::new()
182            .fg_color(Some(Color::Ansi(AnsiColor::Cyan)))
183            .dimmed()
184    }
185
186    /// Bold text style
187    pub fn bold() -> Style {
188        Style::new().effects(Effects::BOLD)
189    }
190
191    /// Bold error style
192    pub fn bold_error() -> Style {
193        Self::error().bold()
194    }
195
196    /// Bold success style
197    pub fn bold_success() -> Style {
198        Self::success().bold()
199    }
200
201    /// Bold warning style
202    pub fn bold_warning() -> Style {
203        Self::warning().bold()
204    }
205
206    /// Header style (bold)
207    pub fn header() -> Style {
208        Style::new().effects(Effects::BOLD)
209    }
210
211    /// Code style (magenta)
212    pub fn code() -> Style {
213        Style::new().fg_color(Some(Color::Ansi(AnsiColor::Magenta)))
214    }
215
216    /// Render style to ANSI string
217    pub fn render(style: &Style) -> String {
218        style.to_string()
219    }
220
221    /// Render reset ANSI string
222    pub fn render_reset() -> String {
223        anstyle::Reset.to_string()
224    }
225}