1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
13#[serde(rename_all = "UPPERCASE")]
14pub struct HsvMultiplier {
15 pub h: f64,
17 pub s: f64,
19 pub v: f64,
21}
22
23impl Default for HsvMultiplier {
24 fn default() -> Self {
25 Self {
26 h: 1.0,
27 s: 1.0,
28 v: 1.0,
29 }
30 }
31}
32
33impl HsvMultiplier {
34 pub fn new(h: f64, s: f64, v: f64) -> Self {
36 Self { h, s, v }
37 }
38
39 pub fn dark() -> Self {
41 Self::new(1.00, 1.50, 0.25)
42 }
43
44 pub fn mid() -> Self {
46 Self::new(1.00, 1.00, 0.50)
47 }
48
49 pub fn symbol() -> Self {
51 Self::new(1.00, 1.00, 1.50)
52 }
53
54 pub fn head() -> Self {
56 Self::new(1.00, 1.00, 1.75)
57 }
58
59 pub fn grey() -> Self {
61 Self::new(1.00, 0.25, 1.37)
62 }
63
64 pub fn bright() -> Self {
66 Self::new(1.00, 0.60, 2.00)
67 }
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
75#[serde(rename_all = "PascalCase")]
76pub struct StyleConfig {
77 #[serde(default = "default_margin")]
80 pub margin: usize,
81
82 #[serde(default = "default_list_indent")]
85 pub list_indent: usize,
86
87 #[serde(default = "default_true")]
90 pub pretty_pad: bool,
91
92 #[serde(default = "default_true")]
95 pub pretty_broken: bool,
96
97 #[serde(default)]
100 pub width: usize,
101
102 #[serde(default = "default_hsv", rename = "HSV")]
107 pub hsv: [f64; 3],
108
109 #[serde(default = "HsvMultiplier::dark")]
111 pub dark: HsvMultiplier,
112
113 #[serde(default = "HsvMultiplier::mid")]
115 pub mid: HsvMultiplier,
116
117 #[serde(default = "HsvMultiplier::symbol")]
119 pub symbol: HsvMultiplier,
120
121 #[serde(default = "HsvMultiplier::head")]
123 pub head: HsvMultiplier,
124
125 #[serde(default = "HsvMultiplier::grey")]
127 pub grey: HsvMultiplier,
128
129 #[serde(default = "HsvMultiplier::bright")]
131 pub bright: HsvMultiplier,
132
133 #[serde(default = "default_syntax")]
136 pub syntax: String,
137}
138
139impl Default for StyleConfig {
140 fn default() -> Self {
141 Self {
142 margin: 2,
143 list_indent: 2,
144 pretty_pad: true,
145 pretty_broken: true,
146 width: 0,
147 hsv: [0.8, 0.5, 0.5],
148 dark: HsvMultiplier::dark(),
149 mid: HsvMultiplier::mid(),
150 symbol: HsvMultiplier::symbol(),
151 head: HsvMultiplier::head(),
152 grey: HsvMultiplier::grey(),
153 bright: HsvMultiplier::bright(),
154 syntax: "native".to_string(),
155 }
156 }
157}
158
159impl StyleConfig {
160 pub fn merge(&mut self, other: &StyleConfig) {
162 self.margin = other.margin;
163 self.list_indent = other.list_indent;
164 self.pretty_pad = other.pretty_pad;
165 self.pretty_broken = other.pretty_broken;
166 self.width = other.width;
167 self.hsv = other.hsv;
168 self.dark = other.dark;
169 self.mid = other.mid;
170 self.symbol = other.symbol;
171 self.head = other.head;
172 self.grey = other.grey;
173 self.bright = other.bright;
174 self.syntax.clone_from(&other.syntax);
175 }
176
177 pub fn base_hsv(&self) -> (f64, f64, f64) {
181 (self.hsv[0] * 360.0, self.hsv[1], self.hsv[2])
183 }
184
185 pub fn effective_width(&self) -> usize {
187 if self.width == 0 {
188 crossterm::terminal::size()
190 .map(|(w, _)| w as usize)
191 .unwrap_or(80)
192 } else {
193 self.width
194 }
195 }
196}
197
198fn default_margin() -> usize {
199 2
200}
201
202fn default_list_indent() -> usize {
203 2
204}
205
206fn default_true() -> bool {
207 true
208}
209
210fn default_hsv() -> [f64; 3] {
211 [0.8, 0.5, 0.5]
212}
213
214fn default_syntax() -> String {
215 "native".to_string()
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 #[test]
223 fn test_default_style() {
224 let style = StyleConfig::default();
225 assert_eq!(style.margin, 2);
226 assert_eq!(style.list_indent, 2);
227 assert!(style.pretty_pad);
228 assert!(style.pretty_broken);
229 assert_eq!(style.width, 0);
230 assert_eq!(style.hsv, [0.8, 0.5, 0.5]);
231 assert_eq!(style.syntax, "native");
232 }
233
234 #[test]
235 fn test_hsv_multiplier_defaults() {
236 let dark = HsvMultiplier::dark();
237 assert!((dark.h - 1.0).abs() < f64::EPSILON);
238 assert!((dark.s - 1.5).abs() < f64::EPSILON);
239 assert!((dark.v - 0.25).abs() < f64::EPSILON);
240
241 let bright = HsvMultiplier::bright();
242 assert!((bright.v - 2.0).abs() < f64::EPSILON);
243 }
244
245 #[test]
246 fn test_serde_pascal_case() {
247 let toml_str = r#"
248 Margin = 4
249 ListIndent = 3
250 PrettyPad = false
251 PrettyBroken = false
252 Width = 100
253 HSV = [0.5, 0.6, 0.7]
254 Dark = { H = 1.0, S = 2.0, V = 0.5 }
255 Syntax = "monokai"
256 "#;
257
258 let style: StyleConfig = toml::from_str(toml_str).unwrap();
259 assert_eq!(style.margin, 4);
260 assert_eq!(style.list_indent, 3);
261 assert!(!style.pretty_pad);
262 assert_eq!(style.width, 100);
263 assert_eq!(style.hsv, [0.5, 0.6, 0.7]);
264 assert!((style.dark.s - 2.0).abs() < f64::EPSILON);
265 assert_eq!(style.syntax, "monokai");
266 }
267
268 #[test]
269 fn test_base_hsv() {
270 let style = StyleConfig::default();
271 let (h, s, v) = style.base_hsv();
272 assert!((h - 288.0).abs() < f64::EPSILON); assert!((s - 0.5).abs() < f64::EPSILON);
274 assert!((v - 0.5).abs() < f64::EPSILON);
275 }
276
277 #[test]
278 fn test_merge() {
279 let mut base = StyleConfig::default();
280 let other = StyleConfig {
281 margin: 5,
282 ..Default::default()
283 };
284
285 base.merge(&other);
286 assert_eq!(base.margin, 5);
287 }
288}