vtcode_ghostty_core/
style.rs1use crate::color::{self, Color};
2
3#[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 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 *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 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 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}