Skip to main content

kitty_rc/commands/
style.rs

1use crate::command::CommandBuilder;
2use crate::error::CommandError;
3use crate::protocol::KittyMessage;
4use serde_json::Map;
5
6pub struct SetBackgroundOpacityCommand {
7    opacity: f32,
8    match_window: Option<String>,
9    match_tab: Option<String>,
10    all: bool,
11    toggle: bool,
12}
13
14impl SetBackgroundOpacityCommand {
15    pub fn new(opacity: f32) -> Self {
16        Self {
17            opacity,
18            match_window: None,
19            match_tab: None,
20            all: false,
21            toggle: false,
22        }
23    }
24
25    pub fn match_window(mut self, spec: impl Into<String>) -> Self {
26        self.match_window = Some(spec.into());
27        self
28    }
29
30    pub fn match_tab(mut self, spec: impl Into<String>) -> Self {
31        self.match_tab = Some(spec.into());
32        self
33    }
34
35    pub fn all(mut self, value: bool) -> Self {
36        self.all = value;
37        self
38    }
39
40    pub fn toggle(mut self, value: bool) -> Self {
41        self.toggle = value;
42        self
43    }
44
45    pub fn build(self) -> Result<KittyMessage, CommandError> {
46        let mut payload = Map::new();
47
48        if self.opacity < 0.0 || self.opacity > 1.0 {
49            return Err(CommandError::ValidationError(
50                "opacity must be between 0.0 and 1.0".to_string(),
51            ));
52        }
53
54        payload.insert("opacity".to_string(), serde_json::json!(self.opacity));
55
56        if let Some(match_window) = self.match_window {
57            payload.insert(
58                "match_window".to_string(),
59                serde_json::Value::String(match_window),
60            );
61        }
62
63        if let Some(match_tab) = self.match_tab {
64            payload.insert(
65                "match_tab".to_string(),
66                serde_json::Value::String(match_tab),
67            );
68        }
69
70        if self.all {
71            payload.insert("all".to_string(), serde_json::Value::Bool(true));
72        }
73
74        if self.toggle {
75            payload.insert("toggle".to_string(), serde_json::Value::Bool(true));
76        }
77
78        Ok(CommandBuilder::new("set-background-opacity")
79            .payload(serde_json::Value::Object(payload))
80            .build())
81    }
82}
83
84pub struct SetBackgroundImageCommand {
85    data: String,
86    match_spec: Option<String>,
87    layout: Option<String>,
88    all: bool,
89    configured: bool,
90}
91
92impl SetBackgroundImageCommand {
93    pub fn new(data: impl Into<String>) -> Self {
94        Self {
95            data: data.into(),
96            match_spec: None,
97            layout: None,
98            all: false,
99            configured: false,
100        }
101    }
102
103    pub fn match_spec(mut self, spec: impl Into<String>) -> Self {
104        self.match_spec = Some(spec.into());
105        self
106    }
107
108    pub fn layout(mut self, value: impl Into<String>) -> Self {
109        self.layout = Some(value.into());
110        self
111    }
112
113    pub fn all(mut self, value: bool) -> Self {
114        self.all = value;
115        self
116    }
117
118    pub fn configured(mut self, value: bool) -> Self {
119        self.configured = value;
120        self
121    }
122
123    pub fn build(self) -> Result<KittyMessage, CommandError> {
124        let mut payload = Map::new();
125
126        if self.data.is_empty() {
127            return Err(CommandError::MissingParameter(
128                "data".to_string(),
129                "set-background-image".to_string(),
130            ));
131        }
132
133        payload.insert("data".to_string(), serde_json::Value::String(self.data));
134
135        if let Some(match_spec) = self.match_spec {
136            payload.insert("match".to_string(), serde_json::Value::String(match_spec));
137        }
138
139        if let Some(layout) = self.layout {
140            payload.insert("layout".to_string(), serde_json::Value::String(layout));
141        }
142
143        if self.all {
144            payload.insert("all".to_string(), serde_json::Value::Bool(true));
145        }
146
147        if self.configured {
148            payload.insert("configured".to_string(), serde_json::Value::Bool(true));
149        }
150
151        Ok(CommandBuilder::new("set-background-image")
152            .payload(serde_json::Value::Object(payload))
153            .build())
154    }
155}
156
157pub struct SetColorsCommand {
158    colors: Map<String, serde_json::Value>,
159    match_window: Option<String>,
160    match_tab: Option<String>,
161    all: bool,
162    configured: bool,
163    reset: bool,
164}
165
166impl SetColorsCommand {
167    pub fn new(colors: Map<String, serde_json::Value>) -> Self {
168        Self {
169            colors,
170            match_window: None,
171            match_tab: None,
172            all: false,
173            configured: false,
174            reset: false,
175        }
176    }
177
178    pub fn match_window(mut self, spec: impl Into<String>) -> Self {
179        self.match_window = Some(spec.into());
180        self
181    }
182
183    pub fn match_tab(mut self, spec: impl Into<String>) -> Self {
184        self.match_tab = Some(spec.into());
185        self
186    }
187
188    pub fn all(mut self, value: bool) -> Self {
189        self.all = value;
190        self
191    }
192
193    pub fn configured(mut self, value: bool) -> Self {
194        self.configured = value;
195        self
196    }
197
198    pub fn reset(mut self, value: bool) -> Self {
199        self.reset = value;
200        self
201    }
202
203    pub fn build(self) -> Result<KittyMessage, CommandError> {
204        let mut payload = Map::new();
205
206        if self.colors.is_empty() {
207            return Err(CommandError::MissingParameter(
208                "colors".to_string(),
209                "set-colors".to_string(),
210            ));
211        }
212
213        payload.insert("colors".to_string(), serde_json::Value::Object(self.colors));
214
215        if let Some(match_window) = self.match_window {
216            payload.insert(
217                "match_window".to_string(),
218                serde_json::Value::String(match_window),
219            );
220        }
221
222        if let Some(match_tab) = self.match_tab {
223            payload.insert(
224                "match_tab".to_string(),
225                serde_json::Value::String(match_tab),
226            );
227        }
228
229        if self.all {
230            payload.insert("all".to_string(), serde_json::Value::Bool(true));
231        }
232
233        if self.configured {
234            payload.insert("configured".to_string(), serde_json::Value::Bool(true));
235        }
236
237        if self.reset {
238            payload.insert("reset".to_string(), serde_json::Value::Bool(true));
239        }
240
241        Ok(CommandBuilder::new("set-colors")
242            .payload(serde_json::Value::Object(payload))
243            .build())
244    }
245}
246
247pub struct SetFontSizeCommand {
248    size: i32,
249    all: bool,
250    increment_op: Option<String>,
251}
252
253impl SetFontSizeCommand {
254    pub fn new(size: i32) -> Self {
255        Self {
256            size,
257            all: false,
258            increment_op: None,
259        }
260    }
261
262    pub fn all(mut self, value: bool) -> Self {
263        self.all = value;
264        self
265    }
266
267    pub fn increment_op(mut self, value: impl Into<String>) -> Self {
268        self.increment_op = Some(value.into());
269        self
270    }
271
272    pub fn build(self) -> Result<KittyMessage, CommandError> {
273        let mut payload = Map::new();
274
275        payload.insert("size".to_string(), serde_json::json!(self.size));
276
277        if self.all {
278            payload.insert("all".to_string(), serde_json::Value::Bool(true));
279        }
280
281        if let Some(increment_op) = self.increment_op {
282            payload.insert(
283                "increment_op".to_string(),
284                serde_json::Value::String(increment_op),
285            );
286        }
287
288        Ok(CommandBuilder::new("set-font-size")
289            .payload(serde_json::Value::Object(payload))
290            .build())
291    }
292}
293
294pub struct SetSpacingCommand {
295    settings: Map<String, serde_json::Value>,
296    match_window: Option<String>,
297    match_tab: Option<String>,
298    all: bool,
299    configured: bool,
300}
301
302impl SetSpacingCommand {
303    pub fn new(settings: Map<String, serde_json::Value>) -> Self {
304        Self {
305            settings,
306            match_window: None,
307            match_tab: None,
308            all: false,
309            configured: false,
310        }
311    }
312
313    pub fn match_window(mut self, spec: impl Into<String>) -> Self {
314        self.match_window = Some(spec.into());
315        self
316    }
317
318    pub fn match_tab(mut self, spec: impl Into<String>) -> Self {
319        self.match_tab = Some(spec.into());
320        self
321    }
322
323    pub fn all(mut self, value: bool) -> Self {
324        self.all = value;
325        self
326    }
327
328    pub fn configured(mut self, value: bool) -> Self {
329        self.configured = value;
330        self
331    }
332
333    pub fn build(self) -> Result<KittyMessage, CommandError> {
334        let mut payload = Map::new();
335
336        if self.settings.is_empty() {
337            return Err(CommandError::MissingParameter(
338                "settings".to_string(),
339                "set-spacing".to_string(),
340            ));
341        }
342
343        payload.insert(
344            "settings".to_string(),
345            serde_json::Value::Object(self.settings),
346        );
347
348        if let Some(match_window) = self.match_window {
349            payload.insert(
350                "match_window".to_string(),
351                serde_json::Value::String(match_window),
352            );
353        }
354
355        if let Some(match_tab) = self.match_tab {
356            payload.insert(
357                "match_tab".to_string(),
358                serde_json::Value::String(match_tab),
359            );
360        }
361
362        if self.all {
363            payload.insert("all".to_string(), serde_json::Value::Bool(true));
364        }
365
366        if self.configured {
367            payload.insert("configured".to_string(), serde_json::Value::Bool(true));
368        }
369
370        Ok(CommandBuilder::new("set-spacing")
371            .payload(serde_json::Value::Object(payload))
372            .build())
373    }
374}
375
376pub struct SetTabColorCommand {
377    colors: Map<String, serde_json::Value>,
378    match_spec: Option<String>,
379    self_tab: bool,
380}
381
382impl SetTabColorCommand {
383    pub fn new(colors: Map<String, serde_json::Value>) -> Self {
384        Self {
385            colors,
386            match_spec: None,
387            self_tab: false,
388        }
389    }
390
391    pub fn match_spec(mut self, spec: impl Into<String>) -> Self {
392        self.match_spec = Some(spec.into());
393        self
394    }
395
396    pub fn self_tab(mut self, value: bool) -> Self {
397        self.self_tab = value;
398        self
399    }
400
401    pub fn build(self) -> Result<KittyMessage, CommandError> {
402        let mut payload = Map::new();
403
404        if self.colors.is_empty() {
405            return Err(CommandError::MissingParameter(
406                "colors".to_string(),
407                "set-tab-color".to_string(),
408            ));
409        }
410
411        payload.insert("colors".to_string(), serde_json::Value::Object(self.colors));
412
413        if let Some(match_spec) = self.match_spec {
414            payload.insert("match".to_string(), serde_json::Value::String(match_spec));
415        }
416
417        if self.self_tab {
418            payload.insert("self".to_string(), serde_json::Value::Bool(true));
419        }
420
421        Ok(CommandBuilder::new("set-tab-color")
422            .payload(serde_json::Value::Object(payload))
423            .build())
424    }
425}
426
427pub struct GetColorsCommand {
428    match_spec: Option<String>,
429    configured: bool,
430}
431
432impl GetColorsCommand {
433    pub fn new() -> Self {
434        Self {
435            match_spec: None,
436            configured: false,
437        }
438    }
439
440    pub fn match_spec(mut self, spec: impl Into<String>) -> Self {
441        self.match_spec = Some(spec.into());
442        self
443    }
444
445    pub fn configured(mut self, value: bool) -> Self {
446        self.configured = value;
447        self
448    }
449
450    pub fn build(self) -> Result<KittyMessage, CommandError> {
451        let mut payload = Map::new();
452
453        if let Some(match_spec) = self.match_spec {
454            payload.insert("match".to_string(), serde_json::Value::String(match_spec));
455        }
456
457        if self.configured {
458            payload.insert("configured".to_string(), serde_json::Value::Bool(true));
459        }
460
461        Ok(CommandBuilder::new("get-colors")
462            .payload(serde_json::Value::Object(payload))
463            .build())
464    }
465}
466
467#[cfg(test)]
468mod tests {
469    use super::*;
470
471    #[test]
472    fn test_set_background_opacity_basic() {
473        let cmd = SetBackgroundOpacityCommand::new(0.5).build();
474        assert!(cmd.is_ok());
475        let msg = cmd.unwrap();
476        assert_eq!(msg.cmd, "set-background-opacity");
477    }
478
479    #[test]
480    fn test_set_background_opacity_invalid() {
481        let cmd = SetBackgroundOpacityCommand::new(1.5).build();
482        assert!(cmd.is_err());
483        if let Err(CommandError::ValidationError(msg)) = cmd {
484            assert!(msg.contains("opacity"));
485        } else {
486            panic!("Expected ValidationError error");
487        }
488    }
489
490    #[test]
491    fn test_set_background_opacity_with_options() {
492        let cmd = SetBackgroundOpacityCommand::new(0.8)
493            .all(true)
494            .toggle(true)
495            .build();
496        assert!(cmd.is_ok());
497        let msg = cmd.unwrap();
498        assert_eq!(msg.cmd, "set-background-opacity");
499    }
500
501    #[test]
502    fn test_set_background_image_basic() {
503        let cmd = SetBackgroundImageCommand::new("base64data").build();
504        assert!(cmd.is_ok());
505        let msg = cmd.unwrap();
506        assert_eq!(msg.cmd, "set-background-image");
507    }
508
509    #[test]
510    fn test_set_background_image_empty() {
511        let cmd = SetBackgroundImageCommand::new("").build();
512        assert!(cmd.is_err());
513        if let Err(CommandError::MissingParameter(field, cmd_name)) = cmd {
514            assert_eq!(field, "data");
515            assert_eq!(cmd_name, "set-background-image");
516        } else {
517            panic!("Expected MissingParameter error");
518        }
519    }
520
521    #[test]
522    fn test_set_background_image_with_options() {
523        let cmd = SetBackgroundImageCommand::new("base64data")
524            .layout("tiled")
525            .all(true)
526            .build();
527        assert!(cmd.is_ok());
528        let msg = cmd.unwrap();
529        assert_eq!(msg.cmd, "set-background-image");
530    }
531
532    #[test]
533    fn test_set_colors_basic() {
534        let mut colors = Map::new();
535        colors.insert(
536            "foreground".to_string(),
537            serde_json::Value::String("#ffffff".to_string()),
538        );
539        let cmd = SetColorsCommand::new(colors).build();
540        assert!(cmd.is_ok());
541        let msg = cmd.unwrap();
542        assert_eq!(msg.cmd, "set-colors");
543    }
544
545    #[test]
546    fn test_set_colors_empty() {
547        let cmd = SetColorsCommand::new(Map::new()).build();
548        assert!(cmd.is_err());
549        if let Err(CommandError::MissingParameter(field, cmd_name)) = cmd {
550            assert_eq!(field, "colors");
551            assert_eq!(cmd_name, "set-colors");
552        } else {
553            panic!("Expected MissingParameter error");
554        }
555    }
556
557    #[test]
558    fn test_set_colors_with_options() {
559        let mut colors = Map::new();
560        colors.insert(
561            "background".to_string(),
562            serde_json::Value::String("#000000".to_string()),
563        );
564        let cmd = SetColorsCommand::new(colors).all(true).reset(true).build();
565        assert!(cmd.is_ok());
566        let msg = cmd.unwrap();
567        assert_eq!(msg.cmd, "set-colors");
568    }
569
570    #[test]
571    fn test_set_font_size_basic() {
572        let cmd = SetFontSizeCommand::new(14).build();
573        assert!(cmd.is_ok());
574        let msg = cmd.unwrap();
575        assert_eq!(msg.cmd, "set-font-size");
576    }
577
578    #[test]
579    fn test_set_font_size_with_options() {
580        let cmd = SetFontSizeCommand::new(16)
581            .all(true)
582            .increment_op("set")
583            .build();
584        assert!(cmd.is_ok());
585        let msg = cmd.unwrap();
586        assert_eq!(msg.cmd, "set-font-size");
587    }
588
589    #[test]
590    fn test_set_spacing_basic() {
591        let mut settings = Map::new();
592        settings.insert("padding".to_string(), serde_json::json!(10));
593        let cmd = SetSpacingCommand::new(settings).build();
594        assert!(cmd.is_ok());
595        let msg = cmd.unwrap();
596        assert_eq!(msg.cmd, "set-spacing");
597    }
598
599    #[test]
600    fn test_set_spacing_empty() {
601        let cmd = SetSpacingCommand::new(Map::new()).build();
602        assert!(cmd.is_err());
603        if let Err(CommandError::MissingParameter(field, cmd_name)) = cmd {
604            assert_eq!(field, "settings");
605            assert_eq!(cmd_name, "set-spacing");
606        } else {
607            panic!("Expected MissingParameter error");
608        }
609    }
610
611    #[test]
612    fn test_set_spacing_with_options() {
613        let mut settings = Map::new();
614        settings.insert("margin".to_string(), serde_json::json!(5));
615        let cmd = SetSpacingCommand::new(settings)
616            .all(true)
617            .configured(true)
618            .build();
619        assert!(cmd.is_ok());
620        let msg = cmd.unwrap();
621        assert_eq!(msg.cmd, "set-spacing");
622    }
623
624    #[test]
625    fn test_set_tab_color_basic() {
626        let mut colors = Map::new();
627        colors.insert(
628            "active_tab_foreground".to_string(),
629            serde_json::Value::String("#ffffff".to_string()),
630        );
631        let cmd = SetTabColorCommand::new(colors).build();
632        assert!(cmd.is_ok());
633        let msg = cmd.unwrap();
634        assert_eq!(msg.cmd, "set-tab-color");
635    }
636
637    #[test]
638    fn test_set_tab_color_empty() {
639        let cmd = SetTabColorCommand::new(Map::new()).build();
640        assert!(cmd.is_err());
641        if let Err(CommandError::MissingParameter(field, cmd_name)) = cmd {
642            assert_eq!(field, "colors");
643            assert_eq!(cmd_name, "set-tab-color");
644        } else {
645            panic!("Expected MissingParameter error");
646        }
647    }
648
649    #[test]
650    fn test_set_tab_color_with_options() {
651        let mut colors = Map::new();
652        colors.insert(
653            "active_tab_background".to_string(),
654            serde_json::Value::String("#000000".to_string()),
655        );
656        let cmd = SetTabColorCommand::new(colors).self_tab(true).build();
657        assert!(cmd.is_ok());
658        let msg = cmd.unwrap();
659        assert_eq!(msg.cmd, "set-tab-color");
660    }
661
662    #[test]
663    fn test_get_colors_basic() {
664        let cmd = GetColorsCommand::new().build();
665        assert!(cmd.is_ok());
666        let msg = cmd.unwrap();
667        assert_eq!(msg.cmd, "get-colors");
668    }
669
670    #[test]
671    fn test_get_colors_with_options() {
672        let cmd = GetColorsCommand::new()
673            .match_spec("id:1")
674            .configured(true)
675            .build();
676        assert!(cmd.is_ok());
677        let msg = cmd.unwrap();
678        assert_eq!(msg.cmd, "get-colors");
679    }
680}