Skip to main content

vtcode_commons/
anstyle_utils.rs

1//! Utilities for working with anstyle types and bridging to ratatui
2
3use anstyle::{AnsiColor, Color as AnsiColorType, Effects, Style as AnsiStyle};
4use ratatui::crossterm::style::Attribute;
5use ratatui::style::{Color, Modifier, Style};
6
7/// Convert anstyle Color to ratatui Color directly
8pub fn ansi_color_to_ratatui_color(color: &AnsiColorType) -> Color {
9    match color {
10        AnsiColorType::Ansi(ansi_color) => match ansi_color {
11            AnsiColor::Black => Color::Black,
12            AnsiColor::Red => Color::Red,
13            AnsiColor::Green => Color::Green,
14            AnsiColor::Yellow => Color::Yellow,
15            AnsiColor::Blue => Color::Blue,
16            AnsiColor::Magenta => Color::DarkGray,
17            AnsiColor::Cyan => Color::Cyan,
18            AnsiColor::White => Color::White,
19            AnsiColor::BrightBlack => Color::DarkGray,
20            AnsiColor::BrightRed => Color::Red,
21            AnsiColor::BrightGreen => Color::Green,
22            AnsiColor::BrightYellow => Color::Yellow,
23            AnsiColor::BrightBlue => Color::Blue,
24            AnsiColor::BrightMagenta => Color::DarkGray,
25            AnsiColor::BrightCyan => Color::Cyan,
26            AnsiColor::BrightWhite => Color::Gray,
27        },
28        AnsiColorType::Rgb(rgb_color) => Color::Rgb(rgb_color.r(), rgb_color.g(), rgb_color.b()),
29        _ => Color::Reset,
30    }
31}
32
33/// Convert an anstyle Style to a ratatui Style using anstyle-crossterm as a bridge
34pub fn anstyle_to_ratatui(anstyle: AnsiStyle) -> Style {
35    let crossterm_style = anstyle_crossterm::to_crossterm(anstyle);
36    let mut style = Style::default();
37
38    if let Some(fg) = crossterm_style.foreground_color {
39        style = style.fg(crossterm_color_to_ratatui(&fg));
40    }
41
42    if let Some(bg) = crossterm_style.background_color {
43        style = style.bg(crossterm_color_to_ratatui(&bg));
44    }
45
46    let attrs = crossterm_style.attributes;
47    apply_attributes(&mut style, attrs);
48
49    style
50}
51
52fn apply_attributes(style: &mut Style, attrs: ratatui::crossterm::style::Attributes) {
53    if attrs.has(Attribute::Bold) {
54        *style = style.add_modifier(Modifier::BOLD);
55    }
56    if attrs.has(Attribute::Italic) {
57        *style = style.add_modifier(Modifier::ITALIC);
58    }
59    if attrs.has(Attribute::Underlined) {
60        *style = style.add_modifier(Modifier::UNDERLINED);
61    }
62    if attrs.has(Attribute::Dim) {
63        *style = style.add_modifier(Modifier::DIM);
64    }
65    if attrs.has(Attribute::Reverse) {
66        *style = style.add_modifier(Modifier::REVERSED);
67    }
68    if attrs.has(Attribute::SlowBlink) || attrs.has(Attribute::RapidBlink) {
69        *style = style.add_modifier(Modifier::SLOW_BLINK);
70    }
71    if attrs.has(Attribute::CrossedOut) {
72        *style = style.add_modifier(Modifier::CROSSED_OUT);
73    }
74}
75
76fn crossterm_color_to_ratatui(color: &ratatui::crossterm::style::Color) -> Color {
77    use ratatui::crossterm::style::Color as CColor;
78    match color {
79        CColor::Reset => Color::Reset,
80        CColor::Black => Color::Black,
81        CColor::DarkGrey => Color::DarkGray,
82        CColor::Red => Color::Red,
83        CColor::DarkRed => Color::Indexed(52),
84        CColor::Green => Color::Green,
85        CColor::DarkGreen => Color::Indexed(22),
86        CColor::Yellow => Color::Yellow,
87        CColor::DarkYellow => Color::Indexed(58),
88        CColor::Blue => Color::Blue,
89        CColor::DarkBlue => Color::Indexed(17),
90        CColor::Magenta => Color::DarkGray,
91        CColor::DarkMagenta => Color::DarkGray,
92        CColor::Cyan => Color::Cyan,
93        CColor::DarkCyan => Color::Indexed(23),
94        CColor::White => Color::White,
95        CColor::Grey => Color::Gray,
96        CColor::Rgb { r, g, b } => Color::Rgb(*r, *g, *b),
97        CColor::AnsiValue(code) => Color::Indexed(*code),
98    }
99}
100
101/// Convert anstyle Effects to ratatui Modifiers
102pub fn ansi_effects_to_ratatui_modifiers(effects: Effects) -> Modifier {
103    let mut modifier = Modifier::empty();
104
105    if effects.contains(Effects::BOLD) {
106        modifier.insert(Modifier::BOLD);
107    }
108    if effects.contains(Effects::DIMMED) {
109        modifier.insert(Modifier::DIM);
110    }
111    if effects.contains(Effects::ITALIC) {
112        modifier.insert(Modifier::ITALIC);
113    }
114    if effects.contains(Effects::UNDERLINE) {
115        modifier.insert(Modifier::UNDERLINED);
116    }
117    if effects.contains(Effects::BLINK) {
118        modifier.insert(Modifier::SLOW_BLINK);
119    }
120    if effects.contains(Effects::INVERT) {
121        modifier.insert(Modifier::REVERSED);
122    }
123    if effects.contains(Effects::STRIKETHROUGH) {
124        modifier.insert(Modifier::CROSSED_OUT);
125    }
126
127    modifier
128}
129
130/// Convert an style directly to a ratatui Style using manual mapping
131pub fn ansi_style_to_ratatui_style(style: AnsiStyle) -> Style {
132    let mut ratatui_style = Style::default();
133
134    if let Some(fg_color) = style.get_fg_color() {
135        ratatui_style = ratatui_style.fg(ansi_color_to_ratatui_color(&fg_color));
136    }
137
138    if let Some(bg_color) = style.get_bg_color() {
139        ratatui_style = ratatui_style.bg(ansi_color_to_ratatui_color(&bg_color));
140    }
141
142    let modifiers = ansi_effects_to_ratatui_modifiers(style.get_effects());
143    ratatui_style = ratatui_style.add_modifier(modifiers);
144
145    ratatui_style
146}
147
148/// A convenience function that combines color, background, and effects into a single ratatui Style
149pub fn build_ratatui_style(
150    fg_color: Option<AnsiColorType>,
151    bg_color: Option<AnsiColorType>,
152    effects: Effects,
153) -> Style {
154    let mut style = Style::default();
155
156    if let Some(fg) = fg_color {
157        style = style.fg(ansi_color_to_ratatui_color(&fg));
158    }
159
160    if let Some(bg) = bg_color {
161        style = style.bg(ansi_color_to_ratatui_color(&bg));
162    }
163
164    let modifiers = ansi_effects_to_ratatui_modifiers(effects);
165    style = style.add_modifier(modifiers);
166
167    style
168}
169
170/// Create a ratatui Style with a foreground color from anstyle
171pub fn fg_color(color: anstyle::Color) -> Style {
172    anstyle_to_ratatui(AnsiStyle::new().fg_color(Some(color)))
173}
174
175/// Create a ratatui Style with a background color from anstyle
176pub fn bg_color(color: anstyle::Color) -> Style {
177    anstyle_to_ratatui(AnsiStyle::new().bg_color(Some(color)))
178}
179
180/// Create a ratatui Style with foreground and background colors
181pub fn fg_bg_colors(fg: anstyle::Color, bg: anstyle::Color) -> Style {
182    anstyle_to_ratatui(AnsiStyle::new().fg_color(Some(fg)).bg_color(Some(bg)))
183}
184
185/// Create a ratatui Style with effects/modifiers
186pub fn with_effects(effects: anstyle::Effects) -> Style {
187    anstyle_to_ratatui(AnsiStyle::new().effects(effects))
188}
189
190/// Create a ratatui Style with foreground color and effects
191pub fn colored_with_effects(color: anstyle::Color, effects: anstyle::Effects) -> Style {
192    anstyle_to_ratatui(AnsiStyle::new().fg_color(Some(color)).effects(effects))
193}
194
195/// Create a ratatui Style with background color and effects
196pub fn bg_colored_with_effects(color: anstyle::Color, effects: anstyle::Effects) -> Style {
197    anstyle_to_ratatui(AnsiStyle::new().bg_color(Some(color)).effects(effects))
198}
199
200/// Create a complete ratatui Style from anstyle colors and effects
201pub fn full_style(
202    fg: Option<anstyle::Color>,
203    bg: Option<anstyle::Color>,
204    effects: anstyle::Effects,
205) -> Style {
206    let mut astyle = AnsiStyle::new();
207    if let Some(c) = fg {
208        astyle = astyle.fg_color(Some(c));
209    }
210    if let Some(c) = bg {
211        astyle = astyle.bg_color(Some(c));
212    }
213    astyle = astyle.effects(effects);
214    anstyle_to_ratatui(astyle)
215}