1use crate::command::CommandBuilder;
2use crate::error::CommandError;
3use crate::protocol::KittyMessage;
4use bon::{bon, Builder};
5use serde::Serialize;
6use serde_json::Map;
7
8fn is_false(v: &bool) -> bool {
9 !v
10}
11
12#[derive(Builder, Serialize)]
13pub struct SetBackgroundOpacityCommand {
14 opacity: f32,
15
16 #[serde(skip_serializing_if = "Option::is_none", default)]
17 match_window: Option<String>,
18
19 #[serde(skip_serializing_if = "Option::is_none", default)]
20 match_tab: Option<String>,
21
22 #[builder(default = false)]
23 #[serde(skip_serializing_if = "is_false")]
24 all: bool,
25
26 #[builder(default = false)]
27 #[serde(skip_serializing_if = "is_false")]
28 toggle: bool,
29}
30
31impl SetBackgroundOpacityCommand {
32 pub fn to_message(self) -> Result<KittyMessage, CommandError> {
33 if self.opacity < 0.0 || self.opacity > 1.0 {
34 return Err(CommandError::ValidationError(
35 "opacity must be between 0.0 and 1.0".to_string(),
36 ));
37 }
38
39 let payload =
40 serde_json::to_value(self).map_err(|e| CommandError::ValidationError(e.to_string()))?;
41
42 Ok(CommandBuilder::new("set-background-opacity")
43 .payload(payload)
44 .build())
45 }
46}
47
48#[derive(Builder, Serialize)]
49pub struct SetBackgroundImageCommand {
50 data: String,
51
52 #[serde(skip_serializing_if = "Option::is_none", rename = "match", default)]
53 match_spec: Option<String>,
54
55 #[serde(skip_serializing_if = "Option::is_none", default)]
56 layout: Option<String>,
57
58 #[builder(default = false)]
59 #[serde(skip_serializing_if = "is_false")]
60 all: bool,
61
62 #[builder(default = false)]
63 #[serde(skip_serializing_if = "is_false")]
64 configured: bool,
65}
66
67impl SetBackgroundImageCommand {
68 pub fn to_message(self) -> Result<KittyMessage, CommandError> {
69 if self.data.is_empty() {
70 return Err(CommandError::MissingParameter(
71 "data".to_string(),
72 "set-background-image".to_string(),
73 ));
74 }
75
76 let payload =
77 serde_json::to_value(self).map_err(|e| CommandError::ValidationError(e.to_string()))?;
78
79 Ok(CommandBuilder::new("set-background-image")
80 .payload(payload)
81 .build())
82 }
83}
84
85#[derive(Builder, Serialize)]
86pub struct SetColorsCommand {
87 colors: Map<String, serde_json::Value>,
88
89 #[serde(skip_serializing_if = "Option::is_none", default)]
90 match_window: Option<String>,
91
92 #[serde(skip_serializing_if = "Option::is_none", default)]
93 match_tab: Option<String>,
94
95 #[builder(default = false)]
96 #[serde(skip_serializing_if = "is_false")]
97 all: bool,
98
99 #[builder(default = false)]
100 #[serde(skip_serializing_if = "is_false")]
101 configured: bool,
102
103 #[builder(default = false)]
104 #[serde(skip_serializing_if = "is_false")]
105 reset: bool,
106}
107
108impl SetColorsCommand {
109 pub fn to_message(self) -> Result<KittyMessage, CommandError> {
110 if self.colors.is_empty() {
111 return Err(CommandError::MissingParameter(
112 "colors".to_string(),
113 "set-colors".to_string(),
114 ));
115 }
116
117 let payload =
118 serde_json::to_value(self).map_err(|e| CommandError::ValidationError(e.to_string()))?;
119
120 Ok(CommandBuilder::new("set-colors").payload(payload).build())
121 }
122}
123
124#[derive(Builder, Serialize)]
125pub struct SetFontSizeCommand {
126 #[serde(rename = "size")]
127 size: f64,
128
129 #[builder(default = false)]
130 #[serde(skip_serializing_if = "is_false")]
131 all: bool,
132
133 #[serde(skip_serializing_if = "Option::is_none", default)]
134 increment_op: Option<String>,
135}
136
137impl SetFontSizeCommand {
138 pub fn to_message(self) -> Result<KittyMessage, CommandError> {
139 let payload =
140 serde_json::to_value(self).map_err(|e| CommandError::ValidationError(e.to_string()))?;
141
142 Ok(CommandBuilder::new("set-font-size")
143 .payload(payload)
144 .build())
145 }
146}
147
148#[derive(Builder, Serialize)]
149pub struct SetSpacingCommand {
150 settings: Map<String, serde_json::Value>,
151
152 #[serde(skip_serializing_if = "Option::is_none", default)]
153 match_window: Option<String>,
154
155 #[serde(skip_serializing_if = "Option::is_none", default)]
156 match_tab: Option<String>,
157
158 #[builder(default = false)]
159 #[serde(skip_serializing_if = "is_false")]
160 all: bool,
161
162 #[builder(default = false)]
163 #[serde(skip_serializing_if = "is_false")]
164 configured: bool,
165}
166
167impl SetSpacingCommand {
168 pub fn to_message(self) -> Result<KittyMessage, CommandError> {
169 if self.settings.is_empty() {
170 return Err(CommandError::MissingParameter(
171 "settings".to_string(),
172 "set-spacing".to_string(),
173 ));
174 }
175
176 let payload =
177 serde_json::to_value(self).map_err(|e| CommandError::ValidationError(e.to_string()))?;
178
179 Ok(CommandBuilder::new("set-spacing").payload(payload).build())
180 }
181}
182
183#[derive(Builder, Serialize)]
184pub struct SetTabColorCommand {
185 colors: Map<String, serde_json::Value>,
186
187 #[serde(skip_serializing_if = "Option::is_none", rename = "match", default)]
188 match_spec: Option<String>,
189
190 #[builder(default = false)]
191 #[serde(skip_serializing_if = "is_false", rename = "self")]
192 self_tab: bool,
193}
194
195impl SetTabColorCommand {
196 pub fn to_message(self) -> Result<KittyMessage, CommandError> {
197 if self.colors.is_empty() {
198 return Err(CommandError::MissingParameter(
199 "colors".to_string(),
200 "set-tab-color".to_string(),
201 ));
202 }
203
204 let payload =
205 serde_json::to_value(self).map_err(|e| CommandError::ValidationError(e.to_string()))?;
206
207 Ok(CommandBuilder::new("set-tab-color")
208 .payload(payload)
209 .build())
210 }
211}
212
213#[derive(Serialize)]
214pub struct GetColorsCommand {
215 #[serde(skip_serializing_if = "Option::is_none", rename = "match", default)]
216 match_spec: Option<String>,
217
218 #[serde(skip_serializing_if = "is_false", default)]
219 configured: bool,
220}
221
222#[bon]
223impl GetColorsCommand {
224 #[builder]
225 pub fn new(match_spec: Option<String>, #[builder(default = false)] configured: bool) -> Self {
226 Self {
227 match_spec,
228 configured,
229 }
230 }
231}
232
233impl GetColorsCommand {
234 pub fn to_message(self) -> Result<KittyMessage, CommandError> {
235 let payload =
236 serde_json::to_value(self).map_err(|e| CommandError::ValidationError(e.to_string()))?;
237
238 Ok(CommandBuilder::new("get-colors").payload(payload).build())
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245
246 #[test]
247 fn test_set_background_opacity_basic() {
248 let cmd = SetBackgroundOpacityCommand::builder()
249 .opacity(0.5)
250 .build()
251 .to_message();
252 assert!(cmd.is_ok());
253 let msg = cmd.unwrap();
254 assert_eq!(msg.cmd, "set-background-opacity");
255 }
256
257 #[test]
258 fn test_set_background_opacity_invalid() {
259 let cmd = SetBackgroundOpacityCommand::builder()
260 .opacity(1.5)
261 .build()
262 .to_message();
263 assert!(cmd.is_err());
264 if let Err(CommandError::ValidationError(msg)) = cmd {
265 assert!(msg.contains("opacity"));
266 } else {
267 panic!("Expected ValidationError error");
268 }
269 }
270
271 #[test]
272 fn test_set_background_opacity_with_options() {
273 let cmd = SetBackgroundOpacityCommand::builder()
274 .opacity(0.8)
275 .all(true)
276 .toggle(true)
277 .build()
278 .to_message();
279 assert!(cmd.is_ok());
280 let msg = cmd.unwrap();
281 assert_eq!(msg.cmd, "set-background-opacity");
282 }
283
284 #[test]
285 fn test_set_background_image_basic() {
286 let cmd = SetBackgroundImageCommand::builder()
287 .data("base64data".to_string())
288 .build()
289 .to_message();
290 assert!(cmd.is_ok());
291 let msg = cmd.unwrap();
292 assert_eq!(msg.cmd, "set-background-image");
293 }
294
295 #[test]
296 fn test_set_background_image_empty() {
297 let cmd = SetBackgroundImageCommand::builder()
298 .data("".to_string())
299 .build()
300 .to_message();
301 assert!(cmd.is_err());
302 if let Err(CommandError::MissingParameter(field, cmd_name)) = cmd {
303 assert_eq!(field, "data");
304 assert_eq!(cmd_name, "set-background-image");
305 } else {
306 panic!("Expected MissingParameter error");
307 }
308 }
309
310 #[test]
311 fn test_set_background_image_with_options() {
312 let cmd = SetBackgroundImageCommand::builder()
313 .data("base64data".to_string())
314 .layout("tiled".to_string())
315 .all(true)
316 .build()
317 .to_message();
318 assert!(cmd.is_ok());
319 let msg = cmd.unwrap();
320 assert_eq!(msg.cmd, "set-background-image");
321 }
322
323 #[test]
324 fn test_set_colors_basic() {
325 let mut colors = Map::new();
326 colors.insert(
327 "foreground".to_string(),
328 serde_json::Value::String("#ffffff".to_string()),
329 );
330 let cmd = SetColorsCommand::builder()
331 .colors(colors)
332 .build()
333 .to_message();
334 assert!(cmd.is_ok());
335 let msg = cmd.unwrap();
336 assert_eq!(msg.cmd, "set-colors");
337 }
338
339 #[test]
340 fn test_set_colors_empty() {
341 let cmd = SetColorsCommand::builder()
342 .colors(Map::new())
343 .build()
344 .to_message();
345 assert!(cmd.is_err());
346 if let Err(CommandError::MissingParameter(field, cmd_name)) = cmd {
347 assert_eq!(field, "colors");
348 assert_eq!(cmd_name, "set-colors");
349 } else {
350 panic!("Expected MissingParameter error");
351 }
352 }
353
354 #[test]
355 fn test_set_colors_with_options() {
356 let mut colors = Map::new();
357 colors.insert(
358 "background".to_string(),
359 serde_json::Value::String("#000000".to_string()),
360 );
361 let cmd = SetColorsCommand::builder()
362 .colors(colors)
363 .all(true)
364 .reset(true)
365 .build()
366 .to_message();
367 assert!(cmd.is_ok());
368 let msg = cmd.unwrap();
369 assert_eq!(msg.cmd, "set-colors");
370 }
371
372 #[test]
373 fn test_set_font_size_basic() {
374 let cmd = SetFontSizeCommand::builder()
375 .size(14.0)
376 .build()
377 .to_message();
378 assert!(cmd.is_ok());
379 let msg = cmd.unwrap();
380 assert_eq!(msg.cmd, "set-font-size");
381 }
382
383 #[test]
384 fn test_set_font_size_with_options() {
385 let cmd = SetFontSizeCommand::builder()
386 .size(16.0)
387 .all(true)
388 .increment_op("set".to_string())
389 .build()
390 .to_message();
391 assert!(cmd.is_ok());
392 let msg = cmd.unwrap();
393 assert_eq!(msg.cmd, "set-font-size");
394 }
395
396 #[test]
397 fn test_set_spacing_basic() {
398 let mut settings = Map::new();
399 settings.insert("padding".to_string(), serde_json::json!(10));
400 let cmd = SetSpacingCommand::builder()
401 .settings(settings)
402 .build()
403 .to_message();
404 assert!(cmd.is_ok());
405 let msg = cmd.unwrap();
406 assert_eq!(msg.cmd, "set-spacing");
407 }
408
409 #[test]
410 fn test_set_spacing_empty() {
411 let cmd = SetSpacingCommand::builder()
412 .settings(Map::new())
413 .build()
414 .to_message();
415 assert!(cmd.is_err());
416 if let Err(CommandError::MissingParameter(field, cmd_name)) = cmd {
417 assert_eq!(field, "settings");
418 assert_eq!(cmd_name, "set-spacing");
419 } else {
420 panic!("Expected MissingParameter error");
421 }
422 }
423
424 #[test]
425 fn test_set_spacing_with_options() {
426 let mut settings = Map::new();
427 settings.insert("margin".to_string(), serde_json::json!(5));
428 let cmd = SetSpacingCommand::builder()
429 .settings(settings)
430 .all(true)
431 .configured(true)
432 .build()
433 .to_message();
434 assert!(cmd.is_ok());
435 let msg = cmd.unwrap();
436 assert_eq!(msg.cmd, "set-spacing");
437 }
438
439 #[test]
440 fn test_set_tab_color_basic() {
441 let mut colors = Map::new();
442 colors.insert(
443 "active_tab_foreground".to_string(),
444 serde_json::Value::String("#ffffff".to_string()),
445 );
446 let cmd = SetTabColorCommand::builder()
447 .colors(colors)
448 .build()
449 .to_message();
450 assert!(cmd.is_ok());
451 let msg = cmd.unwrap();
452 assert_eq!(msg.cmd, "set-tab-color");
453 }
454
455 #[test]
456 fn test_set_tab_color_empty() {
457 let cmd = SetTabColorCommand::builder()
458 .colors(Map::new())
459 .build()
460 .to_message();
461 assert!(cmd.is_err());
462 if let Err(CommandError::MissingParameter(field, cmd_name)) = cmd {
463 assert_eq!(field, "colors");
464 assert_eq!(cmd_name, "set-tab-color");
465 } else {
466 panic!("Expected MissingParameter error");
467 }
468 }
469
470 #[test]
471 fn test_set_tab_color_with_options() {
472 let mut colors = Map::new();
473 colors.insert(
474 "active_tab_background".to_string(),
475 serde_json::Value::String("#000000".to_string()),
476 );
477 let cmd = SetTabColorCommand::builder()
478 .colors(colors)
479 .self_tab(true)
480 .build()
481 .to_message();
482 assert!(cmd.is_ok());
483 let msg = cmd.unwrap();
484 assert_eq!(msg.cmd, "set-tab-color");
485 }
486
487 #[test]
488 fn test_get_colors_basic() {
489 let cmd = GetColorsCommand::builder().build().to_message();
490 assert!(cmd.is_ok());
491 let msg = cmd.unwrap();
492 assert_eq!(msg.cmd, "get-colors");
493 }
494
495 #[test]
496 fn test_get_colors_with_options() {
497 let cmd = GetColorsCommand::builder()
498 .match_spec("id:1".to_string())
499 .configured(true)
500 .build()
501 .to_message();
502 assert!(cmd.is_ok());
503 let msg = cmd.unwrap();
504 assert_eq!(msg.cmd, "get-colors");
505 }
506}