Skip to main content

vtcode_tui/core_tui/
style.rs

1use anstyle::{AnsiColor, Color as AnsiColorEnum, Effects, RgbColor, Style as AnsiStyle};
2use ratatui::style::{Color, Modifier, Style};
3use unicode_width::UnicodeWidthStr;
4
5use crate::ui::theme;
6
7use super::types::{InlineTextStyle, InlineTheme};
8
9fn convert_ansi_color(color: AnsiColorEnum) -> Option<AnsiColorEnum> {
10    Some(match color {
11        AnsiColorEnum::Ansi(ansi) => AnsiColorEnum::Ansi(ansi),
12        AnsiColorEnum::Ansi256(value) => AnsiColorEnum::Ansi256(value),
13        AnsiColorEnum::Rgb(rgb) => AnsiColorEnum::Rgb(rgb),
14    })
15}
16
17fn convert_style_color(style: &AnsiStyle) -> Option<AnsiColorEnum> {
18    style.get_fg_color().and_then(convert_ansi_color)
19}
20
21fn convert_style_bg_color(style: &AnsiStyle) -> Option<AnsiColorEnum> {
22    style.get_bg_color().and_then(convert_ansi_color)
23}
24
25pub fn convert_style(style: AnsiStyle) -> InlineTextStyle {
26    InlineTextStyle {
27        color: convert_style_color(&style),
28        bg_color: convert_style_bg_color(&style),
29        effects: style.get_effects(),
30    }
31}
32
33pub fn theme_from_styles(styles: &theme::ThemeStyles) -> InlineTheme {
34    InlineTheme {
35        foreground: convert_ansi_color(styles.foreground),
36        background: convert_ansi_color(styles.background),
37        primary: convert_style_color(&styles.primary),
38        secondary: convert_style_color(&styles.secondary),
39        tool_accent: convert_style_color(&styles.tool),
40        tool_body: convert_style_color(&styles.tool_detail),
41        pty_body: convert_style_color(&styles.pty_output),
42    }
43}
44
45pub fn measure_text_width(text: &str) -> u16 {
46    UnicodeWidthStr::width(text) as u16
47}
48
49pub fn ratatui_color_from_ansi(color: AnsiColorEnum) -> Color {
50    match color {
51        AnsiColorEnum::Ansi(base) => match base {
52            AnsiColor::Black => Color::Black,
53            AnsiColor::Red => Color::Red,
54            AnsiColor::Green => Color::Green,
55            AnsiColor::Yellow => Color::Yellow,
56            AnsiColor::Blue => Color::Blue,
57            AnsiColor::Magenta => Color::DarkGray,
58            AnsiColor::Cyan => Color::Cyan,
59            AnsiColor::White => Color::White,
60            AnsiColor::BrightBlack => Color::DarkGray,
61            AnsiColor::BrightRed => Color::Red,
62            AnsiColor::BrightGreen => Color::Green,
63            AnsiColor::BrightYellow => Color::Yellow,
64            AnsiColor::BrightBlue => Color::Blue,
65            AnsiColor::BrightMagenta => Color::DarkGray,
66            AnsiColor::BrightCyan => Color::Cyan,
67            AnsiColor::BrightWhite => Color::Gray,
68        },
69        AnsiColorEnum::Ansi256(value) => Color::Indexed(value.index()),
70        AnsiColorEnum::Rgb(RgbColor(red, green, blue)) => Color::Rgb(red, green, blue),
71    }
72}
73
74pub fn ratatui_style_from_inline(
75    style: &InlineTextStyle,
76    fallback: Option<AnsiColorEnum>,
77) -> Style {
78    let mut resolved = Style::default();
79
80    // Foreground color
81    if let Some(color) = style.color.or(fallback) {
82        resolved = resolved.fg(ratatui_color_from_ansi(color));
83    }
84
85    // Background color
86    if let Some(color) = style.bg_color {
87        resolved = resolved.bg(ratatui_color_from_ansi(color));
88    }
89
90    // Effects bitmask
91    if style.effects.contains(Effects::BOLD) {
92        resolved = resolved.add_modifier(Modifier::BOLD);
93    }
94    if style.effects.contains(Effects::ITALIC) {
95        resolved = resolved.add_modifier(Modifier::ITALIC);
96    }
97    if style.effects.contains(Effects::UNDERLINE) {
98        resolved = resolved.add_modifier(Modifier::UNDERLINED);
99    }
100    if style.effects.contains(Effects::DIMMED) {
101        resolved = resolved.add_modifier(Modifier::DIM);
102    }
103
104    resolved
105}
106
107/// PTY output style helper: keep configured color, suppress bold, enforce dimmed output.
108pub fn ratatui_pty_style_from_inline(
109    style: &InlineTextStyle,
110    fallback: Option<AnsiColorEnum>,
111) -> Style {
112    ratatui_style_from_inline(style, fallback)
113        .remove_modifier(Modifier::BOLD)
114        .add_modifier(Modifier::DIM)
115}
116
117/// Convert an `anstyle::Style` directly to a `ratatui::style::Style`.
118///
119/// Provides full effect mapping (bold, italic, underline, dim) in a single step,
120/// eliminating the need for intermediate `InlineTextStyle` conversions.
121pub fn ratatui_style_from_ansi(style: AnsiStyle) -> Style {
122    let inline = convert_style(style);
123    ratatui_style_from_inline(&inline, None)
124}