Skip to main content

vtcode_ghostty_core/
style.rs

1use crate::color::{self, Color};
2
3/// Text rendering attributes for a terminal cell.
4#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5pub struct Style {
6    pub bold: bool,
7    pub faint: bool,
8    pub italic: bool,
9    pub underline: bool,
10    pub blink: bool,
11    pub inverse: bool,
12    pub strikethrough: bool,
13    pub fg: Option<Color>,
14    pub bg: Option<Color>,
15}
16
17impl Default for Style {
18    fn default() -> Self {
19        Self {
20            bold: false,
21            faint: false,
22            italic: false,
23            underline: false,
24            blink: false,
25            inverse: false,
26            strikethrough: false,
27            fg: None,
28            bg: None,
29        }
30    }
31}
32
33impl Style {
34    /// Apply SGR (Select Graphic Rendition) parameters to this style.
35    pub(crate) fn apply_sgr(&mut self, params: &[Option<usize>]) {
36        if params.is_empty() {
37            *self = Self::default();
38            return;
39        }
40
41        let mut i = 0;
42        while i < params.len() {
43            let code = match params[i] {
44                Some(v) => v,
45                None => {
46                    // Missing parameter treats as 0 (reset).
47                    *self = Self::default();
48                    i += 1;
49                    continue;
50                }
51            };
52
53            match code {
54                0 => *self = Self::default(),
55                1 => self.bold = true,
56                2 => self.faint = true,
57                3 => self.italic = true,
58                4 => self.underline = true,
59                5 | 6 => self.blink = true,
60                7 => self.inverse = true,
61                9 => self.strikethrough = true,
62                22 => {
63                    self.bold = false;
64                    self.faint = false;
65                }
66                23 => self.italic = false,
67                24 => self.underline = false,
68                25 => self.blink = false,
69                27 => self.inverse = false,
70                29 => self.strikethrough = false,
71                30..=37 | 90..=97 => {
72                    self.fg = color::ansi_fg(code).map(Color::Ansi);
73                }
74                39 => self.fg = None,
75                40..=47 | 100..=107 => {
76                    self.bg = color::ansi_bg(code).map(Color::Ansi);
77                }
78                49 => self.bg = None,
79                38 => {
80                    // Extended foreground: 38;5;N or 38;2;R;G;B
81                    if let Some(type_param) = params.get(i + 1).and_then(|p| *p) {
82                        match type_param {
83                            5 => {
84                                if let Some(c) =
85                                    Color::from_sgr_params(5, params.get(i + 2..).unwrap_or(&[]))
86                                {
87                                    self.fg = Some(c);
88                                }
89                                i += 3;
90                                continue;
91                            }
92                            2 => {
93                                if let Some(c) =
94                                    Color::from_sgr_params(2, params.get(i + 2..).unwrap_or(&[]))
95                                {
96                                    self.fg = Some(c);
97                                }
98                                i += 5;
99                                continue;
100                            }
101                            _ => {}
102                        }
103                    }
104                }
105                48 => {
106                    // Extended background: 48;5;N or 48;2;R;G;B
107                    if let Some(type_param) = params.get(i + 1).and_then(|p| *p) {
108                        match type_param {
109                            5 => {
110                                if let Some(c) =
111                                    Color::from_sgr_params(5, params.get(i + 2..).unwrap_or(&[]))
112                                {
113                                    self.bg = Some(c);
114                                }
115                                i += 3;
116                                continue;
117                            }
118                            2 => {
119                                if let Some(c) =
120                                    Color::from_sgr_params(2, params.get(i + 2..).unwrap_or(&[]))
121                                {
122                                    self.bg = Some(c);
123                                }
124                                i += 5;
125                                continue;
126                            }
127                            _ => {}
128                        }
129                    }
130                }
131                _ => {}
132            }
133            i += 1;
134        }
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141    use crate::color::{AnsiColor, Color};
142
143    #[test]
144    fn sgr_reset() {
145        let mut style = Style {
146            bold: true,
147            fg: Some(Color::Ansi(AnsiColor::Red)),
148            ..Style::default()
149        };
150        style.apply_sgr(&[Some(0)]);
151        assert_eq!(style, Style::default());
152    }
153
154    #[test]
155    fn sgr_bold_and_color() {
156        let mut style = Style::default();
157        style.apply_sgr(&[Some(1), Some(31)]);
158        assert!(style.bold);
159        assert_eq!(style.fg, Some(Color::Ansi(AnsiColor::Red)));
160    }
161
162    #[test]
163    fn sgr_256_color() {
164        let mut style = Style::default();
165        style.apply_sgr(&[Some(38), Some(5), Some(196)]);
166        assert_eq!(style.fg, Some(Color::Indexed(196)));
167    }
168
169    #[test]
170    fn sgr_rgb_color() {
171        let mut style = Style::default();
172        style.apply_sgr(&[Some(38), Some(2), Some(255), Some(128), Some(0)]);
173        assert_eq!(
174            style.fg,
175            Some(Color::Rgb {
176                r: 255,
177                g: 128,
178                b: 0
179            })
180        );
181    }
182
183    #[test]
184    fn sgr_individual_resets() {
185        let mut style = Style {
186            bold: true,
187            italic: true,
188            underline: true,
189            ..Style::default()
190        };
191        style.apply_sgr(&[Some(22), Some(23)]);
192        assert!(!style.bold);
193        assert!(!style.faint);
194        assert!(!style.italic);
195        assert!(style.underline);
196    }
197}