Skip to main content

kitty_rc/commands/
style.rs

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}