Skip to main content

libghostty_vt/
style.rs

1//! Terminal cell style attributes.
2//!
3//! A style describes the visual attributes of a terminal cell, including
4//! foreground, background, and underline colors, as well as flags for bold,
5//! italic, underline, and other text decorations.
6use std::mem::MaybeUninit;
7
8use crate::{
9    error::{Error, Result},
10    ffi,
11};
12
13/// Style identifier type.
14///
15/// Used to look up the full style from a grid reference.
16/// Obtain this from a cell via [`Cell::style_id`][crate::screen::Cell::style_id].
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub struct Id(pub(crate) ffi::StyleId);
19
20/// Terminal cell style attributes.
21///
22/// A style describes the visual attributes of a terminal cell, including
23/// foreground, background, and underline colors, as well as flags for bold,
24/// italic, underline, and other text decorations.
25#[expect(
26    clippy::struct_excessive_bools,
27    reason = "style attributes should be just a bunch of bools"
28)]
29#[expect(missing_docs, reason = "self-explanatory")]
30#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31pub struct Style {
32    pub fg_color: StyleColor,
33    pub bg_color: StyleColor,
34    pub underline_color: StyleColor,
35    pub bold: bool,
36    pub italic: bool,
37    pub faint: bool,
38    pub blink: bool,
39    pub inverse: bool,
40    pub invisible: bool,
41    pub strikethrough: bool,
42    pub overline: bool,
43    pub underline: Underline,
44}
45
46impl Style {
47    /// Check if a style is the default style.
48    ///
49    /// Returns true if all colors are unset and all flags are off.
50    #[must_use]
51    pub fn is_default(self) -> bool {
52        let raw = ffi::Style::from(self);
53        unsafe { ffi::ghostty_style_is_default(&raw const raw) }
54    }
55}
56
57impl Default for Style {
58    fn default() -> Self {
59        let mut style = MaybeUninit::zeroed();
60        unsafe {
61            ffi::ghostty_style_default(style.as_mut_ptr());
62        }
63
64        // SAFETY: We trust the function above to initialize everything correctly
65        Self::try_from(unsafe { style.assume_init() })
66            .expect("ghostty_style_default to init valid Style")
67    }
68}
69
70/// A color used in a style attribute.
71#[derive(Clone, Copy, Debug, PartialEq, Eq)]
72pub enum StyleColor {
73    /// Unset.
74    None,
75    /// Palette index.
76    Palette(PaletteIndex),
77    /// Direct RGB value.
78    Rgb(RgbColor),
79}
80
81/// RGB color value.
82#[repr(C)]
83#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
84pub struct RgbColor {
85    /// Red color component (0-255)
86    pub r: u8,
87    /// Green color component (0-255)
88    pub g: u8,
89    /// Blue color component (0-255)
90    pub b: u8,
91}
92
93/// Palette color index (0-255).
94#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
95pub struct PaletteIndex(pub ffi::ColorPaletteIndex);
96
97impl PaletteIndex {
98    #![expect(missing_docs, reason = "self-explanatory")]
99    pub const BLACK: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_BLACK);
100    pub const RED: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_RED);
101    pub const GREEN: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_GREEN);
102    pub const YELLOW: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_YELLOW);
103    pub const BLUE: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_BLUE);
104    pub const MAGENTA: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_MAGENTA);
105    pub const CYAN: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_CYAN);
106    pub const WHITE: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_WHITE);
107    pub const BRIGHT_BLACK: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_BRIGHT_BLACK);
108    pub const BRIGHT_RED: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_BRIGHT_RED);
109    pub const BRIGHT_GREEN: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_BRIGHT_GREEN);
110    pub const BRIGHT_YELLOW: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_BRIGHT_YELLOW);
111    pub const BRIGHT_BLUE: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_BRIGHT_BLUE);
112    pub const BRIGHT_MAGENTA: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_BRIGHT_MAGENTA);
113    pub const BRIGHT_CYAN: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_BRIGHT_CYAN);
114    pub const BRIGHT_WHITE: PaletteIndex = PaletteIndex(ffi::COLOR_NAMED_BRIGHT_WHITE);
115}
116
117/// Underline style types.
118#[repr(u32)]
119#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, int_enum::IntEnum)]
120#[non_exhaustive]
121#[expect(missing_docs, reason = "self-explanatory")]
122pub enum Underline {
123    None = ffi::SgrUnderline::NONE,
124    Single = ffi::SgrUnderline::SINGLE,
125    Double = ffi::SgrUnderline::DOUBLE,
126    Curly = ffi::SgrUnderline::CURLY,
127    Dotted = ffi::SgrUnderline::DOTTED,
128    Dashed = ffi::SgrUnderline::DASHED,
129}
130
131//----------------------------------
132// Conversion to and from FFI types
133//----------------------------------
134
135impl TryFrom<ffi::Style> for Style {
136    type Error = Error;
137    fn try_from(value: ffi::Style) -> Result<Self> {
138        Ok(Self {
139            fg_color: StyleColor::try_from(value.fg_color)?,
140            bg_color: StyleColor::try_from(value.bg_color)?,
141            underline_color: StyleColor::try_from(value.underline_color)?,
142            bold: value.bold,
143            italic: value.italic,
144            faint: value.faint,
145            blink: value.blink,
146            inverse: value.inverse,
147            invisible: value.invisible,
148            strikethrough: value.strikethrough,
149            overline: value.overline,
150            #[expect(clippy::cast_sign_loss, reason = "bindgen ain't perfect")]
151            underline: Underline::try_from(value.underline as u32)
152                .map_err(|_| Error::InvalidValue)?,
153        })
154    }
155}
156
157impl From<Style> for ffi::Style {
158    fn from(value: Style) -> Self {
159        Self {
160            size: std::mem::size_of::<Self>(),
161            fg_color: value.fg_color.into(),
162            bg_color: value.bg_color.into(),
163            underline_color: value.underline_color.into(),
164            bold: value.bold,
165            italic: value.italic,
166            faint: value.faint,
167            blink: value.blink,
168            inverse: value.inverse,
169            invisible: value.invisible,
170            strikethrough: value.strikethrough,
171            overline: value.overline,
172            #[expect(clippy::cast_possible_wrap, reason = "bindgen ain't perfect")]
173            underline: u32::from(value.underline) as i32,
174        }
175    }
176}
177
178impl TryFrom<ffi::StyleColor> for StyleColor {
179    type Error = Error;
180    fn try_from(value: ffi::StyleColor) -> Result<Self> {
181        Ok(match value.tag {
182            ffi::StyleColorTag::NONE => Self::None,
183            ffi::StyleColorTag::PALETTE => {
184                Self::Palette(PaletteIndex(unsafe { value.value.palette }))
185            }
186            ffi::StyleColorTag::RGB => Self::Rgb(unsafe { value.value.rgb }.into()),
187            _ => return Err(Error::InvalidValue),
188        })
189    }
190}
191
192impl From<StyleColor> for ffi::StyleColor {
193    fn from(value: StyleColor) -> Self {
194        match value {
195            StyleColor::None => Self {
196                tag: ffi::StyleColorTag::NONE,
197                value: ffi::StyleColorValue::default(),
198            },
199            StyleColor::Palette(PaletteIndex(palette)) => Self {
200                tag: ffi::StyleColorTag::PALETTE,
201                value: ffi::StyleColorValue { palette },
202            },
203            StyleColor::Rgb(rgb) => Self {
204                tag: ffi::StyleColorTag::RGB,
205                value: ffi::StyleColorValue { rgb: rgb.into() },
206            },
207        }
208    }
209}
210
211impl From<ffi::ColorRgb> for RgbColor {
212    fn from(value: ffi::ColorRgb) -> Self {
213        let ffi::ColorRgb { r, g, b } = value;
214        Self { r, g, b }
215    }
216}
217
218impl From<RgbColor> for ffi::ColorRgb {
219    fn from(value: RgbColor) -> Self {
220        let RgbColor { r, g, b } = value;
221        Self { r, g, b }
222    }
223}