Skip to main content

kitty_rc/commands/
process.rs

1use crate::command::CommandBuilder;
2use crate::error::CommandError;
3use crate::protocol::KittyMessage;
4use serde::Deserialize;
5use serde_json::Map;
6
7#[derive(Debug, Deserialize)]
8pub struct ProcessInfo {
9    pub pid: Option<u64>,
10    #[serde(default)]
11    pub cmdline: Vec<String>,
12    pub cwd: Option<String>,
13}
14
15pub struct RunCommand {
16    data: Option<String>,
17    cmdline: Option<String>,
18    env: Option<Map<String, serde_json::Value>>,
19    allow_remote_control: bool,
20    remote_control_password: Option<String>,
21}
22
23impl RunCommand {
24    pub fn new() -> Self {
25        Self {
26            data: None,
27            cmdline: None,
28            env: None,
29            allow_remote_control: false,
30            remote_control_password: None,
31        }
32    }
33
34    pub fn data(mut self, value: impl Into<String>) -> Self {
35        self.data = Some(value.into());
36        self
37    }
38
39    pub fn cmdline(mut self, value: impl Into<String>) -> Self {
40        self.cmdline = Some(value.into());
41        self
42    }
43
44    pub fn env(mut self, value: Map<String, serde_json::Value>) -> Self {
45        self.env = Some(value);
46        self
47    }
48
49    pub fn allow_remote_control(mut self, value: bool) -> Self {
50        self.allow_remote_control = value;
51        self
52    }
53
54    pub fn remote_control_password(mut self, value: impl Into<String>) -> Self {
55        self.remote_control_password = Some(value.into());
56        self
57    }
58
59    pub fn build(self) -> Result<KittyMessage, CommandError> {
60        let mut payload = Map::new();
61
62        if let Some(data) = self.data {
63            payload.insert("data".to_string(), serde_json::Value::String(data));
64        }
65
66        if let Some(cmdline) = self.cmdline {
67            payload.insert("cmdline".to_string(), serde_json::Value::String(cmdline));
68        }
69
70        if let Some(env) = self.env {
71            payload.insert("env".to_string(), serde_json::Value::Object(env));
72        }
73
74        if self.allow_remote_control {
75            payload.insert("allow_remote_control".to_string(), serde_json::Value::Bool(true));
76        }
77
78        if let Some(remote_control_password) = self.remote_control_password {
79            payload.insert("remote_control_password".to_string(), serde_json::Value::String(remote_control_password));
80        }
81
82        Ok(CommandBuilder::new("run")
83            .payload(serde_json::Value::Object(payload))
84            .build())
85    }
86}
87
88pub struct KittenCommand {
89    args: Option<String>,
90    match_spec: Option<String>,
91}
92
93impl KittenCommand {
94    pub fn new() -> Self {
95        Self {
96            args: None,
97            match_spec: None,
98        }
99    }
100
101    pub fn args(mut self, value: impl Into<String>) -> Self {
102        self.args = Some(value.into());
103        self
104    }
105
106    pub fn match_spec(mut self, spec: impl Into<String>) -> Self {
107        self.match_spec = Some(spec.into());
108        self
109    }
110
111    pub fn build(self) -> Result<KittyMessage, CommandError> {
112        let mut payload = Map::new();
113
114        if let Some(args) = self.args {
115            payload.insert("args".to_string(), serde_json::Value::String(args));
116        }
117
118        if let Some(match_spec) = self.match_spec {
119            payload.insert("match".to_string(), serde_json::Value::String(match_spec));
120        }
121
122        Ok(CommandBuilder::new("kitten")
123            .payload(serde_json::Value::Object(payload))
124            .build())
125    }
126}
127
128pub struct LaunchCommand {
129    args: Option<String>,
130    window_title: Option<String>,
131    cwd: Option<String>,
132    env: Option<Map<String, serde_json::Value>>,
133    var: Option<Map<String, serde_json::Value>>,
134    tab_title: Option<String>,
135    window_type: Option<String>,
136    keep_focus: bool,
137    copy_colors: bool,
138    copy_cmdline: bool,
139    copy_env: bool,
140    hold: bool,
141    location: Option<String>,
142    allow_remote_control: bool,
143    remote_control_password: Option<String>,
144    stdin_source: Option<String>,
145    stdin_add_formatting: bool,
146    stdin_add_line_wrap_markers: bool,
147    spacing: Option<String>,
148    marker: Option<String>,
149    logo: Option<String>,
150    logo_position: Option<String>,
151    logo_alpha: Option<f32>,
152    self_window: bool,
153    os_window_title: Option<String>,
154    os_window_name: Option<String>,
155    os_window_class: Option<String>,
156    os_window_state: Option<String>,
157    color: Option<String>,
158    watcher: Option<String>,
159    bias: Option<i32>,
160}
161
162impl LaunchCommand {
163    pub fn new() -> Self {
164        Self {
165            args: None,
166            window_title: None,
167            cwd: None,
168            env: None,
169            var: None,
170            tab_title: None,
171            window_type: None,
172            keep_focus: false,
173            copy_colors: false,
174            copy_cmdline: false,
175            copy_env: false,
176            hold: false,
177            location: None,
178            allow_remote_control: false,
179            remote_control_password: None,
180            stdin_source: None,
181            stdin_add_formatting: false,
182            stdin_add_line_wrap_markers: false,
183            spacing: None,
184            marker: None,
185            logo: None,
186            logo_position: None,
187            logo_alpha: None,
188            self_window: false,
189            os_window_title: None,
190            os_window_name: None,
191            os_window_class: None,
192            os_window_state: None,
193            color: None,
194            watcher: None,
195            bias: None,
196        }
197    }
198
199    pub fn args(mut self, value: impl Into<String>) -> Self {
200        self.args = Some(value.into());
201        self
202    }
203
204    pub fn window_title(mut self, value: impl Into<String>) -> Self {
205        self.window_title = Some(value.into());
206        self
207    }
208
209    pub fn cwd(mut self, value: impl Into<String>) -> Self {
210        self.cwd = Some(value.into());
211        self
212    }
213
214    pub fn env(mut self, value: Map<String, serde_json::Value>) -> Self {
215        self.env = Some(value);
216        self
217    }
218
219    pub fn var(mut self, value: Map<String, serde_json::Value>) -> Self {
220        self.var = Some(value);
221        self
222    }
223
224    pub fn tab_title(mut self, value: impl Into<String>) -> Self {
225        self.tab_title = Some(value.into());
226        self
227    }
228
229    pub fn window_type(mut self, value: impl Into<String>) -> Self {
230        self.window_type = Some(value.into());
231        self
232    }
233
234    pub fn keep_focus(mut self, value: bool) -> Self {
235        self.keep_focus = value;
236        self
237    }
238
239    pub fn copy_colors(mut self, value: bool) -> Self {
240        self.copy_colors = value;
241        self
242    }
243
244    pub fn copy_cmdline(mut self, value: bool) -> Self {
245        self.copy_cmdline = value;
246        self
247    }
248
249    pub fn copy_env(mut self, value: bool) -> Self {
250        self.copy_env = value;
251        self
252    }
253
254    pub fn hold(mut self, value: bool) -> Self {
255        self.hold = value;
256        self
257    }
258
259    pub fn location(mut self, value: impl Into<String>) -> Self {
260        self.location = Some(value.into());
261        self
262    }
263
264    pub fn allow_remote_control(mut self, value: bool) -> Self {
265        self.allow_remote_control = value;
266        self
267    }
268
269    pub fn remote_control_password(mut self, value: impl Into<String>) -> Self {
270        self.remote_control_password = Some(value.into());
271        self
272    }
273
274    pub fn stdin_source(mut self, value: impl Into<String>) -> Self {
275        self.stdin_source = Some(value.into());
276        self
277    }
278
279    pub fn stdin_add_formatting(mut self, value: bool) -> Self {
280        self.stdin_add_formatting = value;
281        self
282    }
283
284    pub fn stdin_add_line_wrap_markers(mut self, value: bool) -> Self {
285        self.stdin_add_line_wrap_markers = value;
286        self
287    }
288
289    pub fn spacing(mut self, value: impl Into<String>) -> Self {
290        self.spacing = Some(value.into());
291        self
292    }
293
294    pub fn marker(mut self, value: impl Into<String>) -> Self {
295        self.marker = Some(value.into());
296        self
297    }
298
299    pub fn logo(mut self, value: impl Into<String>) -> Self {
300        self.logo = Some(value.into());
301        self
302    }
303
304    pub fn logo_position(mut self, value: impl Into<String>) -> Self {
305        self.logo_position = Some(value.into());
306        self
307    }
308
309    pub fn logo_alpha(mut self, value: f32) -> Self {
310        self.logo_alpha = Some(value);
311        self
312    }
313
314    pub fn self_window(mut self, value: bool) -> Self {
315        self.self_window = value;
316        self
317    }
318
319    pub fn os_window_title(mut self, value: impl Into<String>) -> Self {
320        self.os_window_title = Some(value.into());
321        self
322    }
323
324    pub fn os_window_name(mut self, value: impl Into<String>) -> Self {
325        self.os_window_name = Some(value.into());
326        self
327    }
328
329    pub fn os_window_class(mut self, value: impl Into<String>) -> Self {
330        self.os_window_class = Some(value.into());
331        self
332    }
333
334    pub fn os_window_state(mut self, value: impl Into<String>) -> Self {
335        self.os_window_state = Some(value.into());
336        self
337    }
338
339    pub fn color(mut self, value: impl Into<String>) -> Self {
340        self.color = Some(value.into());
341        self
342    }
343
344    pub fn watcher(mut self, value: impl Into<String>) -> Self {
345        self.watcher = Some(value.into());
346        self
347    }
348
349    pub fn bias(mut self, value: i32) -> Self {
350        self.bias = Some(value);
351        self
352    }
353
354    pub fn build(self) -> Result<KittyMessage, CommandError> {
355        let mut payload = Map::new();
356
357        if let Some(args) = self.args {
358            payload.insert("args".to_string(), serde_json::Value::String(args));
359        }
360
361        if let Some(window_title) = self.window_title {
362            payload.insert("window_title".to_string(), serde_json::Value::String(window_title));
363        }
364
365        if let Some(cwd) = self.cwd {
366            payload.insert("cwd".to_string(), serde_json::Value::String(cwd));
367        }
368
369        if let Some(env) = self.env {
370            payload.insert("env".to_string(), serde_json::Value::Object(env));
371        }
372
373        if let Some(var) = self.var {
374            payload.insert("var".to_string(), serde_json::Value::Object(var));
375        }
376
377        if let Some(tab_title) = self.tab_title {
378            payload.insert("tab_title".to_string(), serde_json::Value::String(tab_title));
379        }
380
381        if let Some(window_type) = self.window_type {
382            payload.insert("window_type".to_string(), serde_json::Value::String(window_type));
383        }
384
385        if self.keep_focus {
386            payload.insert("keep_focus".to_string(), serde_json::Value::Bool(true));
387        }
388
389        if self.copy_colors {
390            payload.insert("copy_colors".to_string(), serde_json::Value::Bool(true));
391        }
392
393        if self.copy_cmdline {
394            payload.insert("copy_cmdline".to_string(), serde_json::Value::Bool(true));
395        }
396
397        if self.copy_env {
398            payload.insert("copy_env".to_string(), serde_json::Value::Bool(true));
399        }
400
401        if self.hold {
402            payload.insert("hold".to_string(), serde_json::Value::Bool(true));
403        }
404
405        if let Some(location) = self.location {
406            payload.insert("location".to_string(), serde_json::Value::String(location));
407        }
408
409        if self.allow_remote_control {
410            payload.insert("allow_remote_control".to_string(), serde_json::Value::Bool(true));
411        }
412
413        if let Some(remote_control_password) = self.remote_control_password {
414            payload.insert("remote_control_password".to_string(), serde_json::Value::String(remote_control_password));
415        }
416
417        if let Some(stdin_source) = self.stdin_source {
418            payload.insert("stdin_source".to_string(), serde_json::Value::String(stdin_source));
419        }
420
421        if self.stdin_add_formatting {
422            payload.insert("stdin_add_formatting".to_string(), serde_json::Value::Bool(true));
423        }
424
425        if self.stdin_add_line_wrap_markers {
426            payload.insert("stdin_add_line_wrap_markers".to_string(), serde_json::Value::Bool(true));
427        }
428
429        if let Some(spacing) = self.spacing {
430            payload.insert("spacing".to_string(), serde_json::Value::String(spacing));
431        }
432
433        if let Some(marker) = self.marker {
434            payload.insert("marker".to_string(), serde_json::Value::String(marker));
435        }
436
437        if let Some(logo) = self.logo {
438            payload.insert("logo".to_string(), serde_json::Value::String(logo));
439        }
440
441        if let Some(logo_position) = self.logo_position {
442            payload.insert("logo_position".to_string(), serde_json::Value::String(logo_position));
443        }
444
445        if let Some(logo_alpha) = self.logo_alpha {
446            payload.insert("logo_alpha".to_string(), serde_json::json!(logo_alpha));
447        }
448
449        if self.self_window {
450            payload.insert("self".to_string(), serde_json::Value::Bool(true));
451        }
452
453        if let Some(os_window_title) = self.os_window_title {
454            payload.insert("os_window_title".to_string(), serde_json::Value::String(os_window_title));
455        }
456
457        if let Some(os_window_name) = self.os_window_name {
458            payload.insert("os_window_name".to_string(), serde_json::Value::String(os_window_name));
459        }
460
461        if let Some(os_window_class) = self.os_window_class {
462            payload.insert("os_window_class".to_string(), serde_json::Value::String(os_window_class));
463        }
464
465        if let Some(os_window_state) = self.os_window_state {
466            payload.insert("os_window_state".to_string(), serde_json::Value::String(os_window_state));
467        }
468
469        if let Some(color) = self.color {
470            payload.insert("color".to_string(), serde_json::Value::String(color));
471        }
472
473        if let Some(watcher) = self.watcher {
474            payload.insert("watcher".to_string(), serde_json::Value::String(watcher));
475        }
476
477        if let Some(bias) = self.bias {
478            payload.insert("bias".to_string(), serde_json::json!(bias));
479        }
480
481        Ok(CommandBuilder::new("launch")
482            .payload(serde_json::Value::Object(payload))
483            .build())
484    }
485}
486
487pub struct EnvCommand {
488    env: Map<String, serde_json::Value>,
489}
490
491impl EnvCommand {
492    pub fn new(env: Map<String, serde_json::Value>) -> Self {
493        Self { env }
494    }
495
496    pub fn build(self) -> Result<KittyMessage, CommandError> {
497        let mut payload = Map::new();
498
499        if self.env.is_empty() {
500            return Err(CommandError::MissingParameter("env".to_string(), "env".to_string()));
501        }
502
503        payload.insert("env".to_string(), serde_json::Value::Object(self.env));
504
505        Ok(CommandBuilder::new("env")
506            .payload(serde_json::Value::Object(payload))
507            .build())
508    }
509}
510
511pub struct SetUserVarsCommand {
512    var: Vec<String>,
513    match_spec: Option<String>,
514}
515
516impl SetUserVarsCommand {
517    pub fn new(var: Vec<String>) -> Self {
518        Self {
519            var,
520            match_spec: None,
521        }
522    }
523
524    pub fn match_spec(mut self, spec: impl Into<String>) -> Self {
525        self.match_spec = Some(spec.into());
526        self
527    }
528
529    pub fn build(self) -> Result<KittyMessage, CommandError> {
530        let mut payload = Map::new();
531
532        if self.var.is_empty() {
533            return Err(CommandError::MissingParameter("var".to_string(), "set-user-vars".to_string()));
534        }
535
536        payload.insert("var".to_string(), serde_json::json!(self.var));
537
538        if let Some(match_spec) = self.match_spec {
539            payload.insert("match".to_string(), serde_json::Value::String(match_spec));
540        }
541
542        Ok(CommandBuilder::new("set-user-vars")
543            .payload(serde_json::Value::Object(payload))
544            .build())
545    }
546}
547
548pub struct LoadConfigCommand {
549    paths: Vec<String>,
550    override_config: bool,
551    ignore_overrides: bool,
552}
553
554impl LoadConfigCommand {
555    pub fn new(paths: Vec<String>) -> Self {
556        Self {
557            paths,
558            override_config: false,
559            ignore_overrides: false,
560        }
561    }
562
563    pub fn override_config(mut self, value: bool) -> Self {
564        self.override_config = value;
565        self
566    }
567
568    pub fn ignore_overrides(mut self, value: bool) -> Self {
569        self.ignore_overrides = value;
570        self
571    }
572
573    pub fn build(self) -> Result<KittyMessage, CommandError> {
574        let mut payload = Map::new();
575
576        if self.paths.is_empty() {
577            return Err(CommandError::MissingParameter("paths".to_string(), "load-config".to_string()));
578        }
579
580        payload.insert("paths".to_string(), serde_json::json!(self.paths));
581
582        if self.override_config {
583            payload.insert("override".to_string(), serde_json::Value::Bool(true));
584        }
585
586        if self.ignore_overrides {
587            payload.insert("ignore_overrides".to_string(), serde_json::Value::Bool(true));
588        }
589
590        Ok(CommandBuilder::new("load-config")
591            .payload(serde_json::Value::Object(payload))
592            .build())
593    }
594}
595
596pub struct ResizeOSWindowCommand {
597    match_spec: Option<String>,
598    self_window: bool,
599    incremental: bool,
600    action: Option<String>,
601    unit: Option<String>,
602    width: Option<i32>,
603    height: Option<i32>,
604}
605
606impl ResizeOSWindowCommand {
607    pub fn new() -> Self {
608        Self {
609            match_spec: None,
610            self_window: false,
611            incremental: false,
612            action: None,
613            unit: None,
614            width: None,
615            height: None,
616        }
617    }
618
619    pub fn match_spec(mut self, spec: impl Into<String>) -> Self {
620        self.match_spec = Some(spec.into());
621        self
622    }
623
624    pub fn self_window(mut self, value: bool) -> Self {
625        self.self_window = value;
626        self
627    }
628
629    pub fn incremental(mut self, value: bool) -> Self {
630        self.incremental = value;
631        self
632    }
633
634    pub fn action(mut self, value: impl Into<String>) -> Self {
635        self.action = Some(value.into());
636        self
637    }
638
639    pub fn unit(mut self, value: impl Into<String>) -> Self {
640        self.unit = Some(value.into());
641        self
642    }
643
644    pub fn width(mut self, value: i32) -> Self {
645        self.width = Some(value);
646        self
647    }
648
649    pub fn height(mut self, value: i32) -> Self {
650        self.height = Some(value);
651        self
652    }
653
654    pub fn build(self) -> Result<KittyMessage, CommandError> {
655        let mut payload = Map::new();
656
657        if let Some(match_spec) = self.match_spec {
658            payload.insert("match".to_string(), serde_json::Value::String(match_spec));
659        }
660
661        if self.self_window {
662            payload.insert("self".to_string(), serde_json::Value::Bool(true));
663        }
664
665        if self.incremental {
666            payload.insert("incremental".to_string(), serde_json::Value::Bool(true));
667        }
668
669        if let Some(action) = self.action {
670            payload.insert("action".to_string(), serde_json::Value::String(action));
671        }
672
673        if let Some(unit) = self.unit {
674            payload.insert("unit".to_string(), serde_json::Value::String(unit));
675        }
676
677        if let Some(width) = self.width {
678            payload.insert("width".to_string(), serde_json::json!(width));
679        }
680
681        if let Some(height) = self.height {
682            payload.insert("height".to_string(), serde_json::json!(height));
683        }
684
685        Ok(CommandBuilder::new("resize-os-window")
686            .payload(serde_json::Value::Object(payload))
687            .build())
688    }
689}
690
691pub struct DisableLigaturesCommand {
692    strategy: Option<String>,
693    match_window: Option<String>,
694    match_tab: Option<String>,
695    all: bool,
696}
697
698impl DisableLigaturesCommand {
699    pub fn new() -> Self {
700        Self {
701            strategy: None,
702            match_window: None,
703            match_tab: None,
704            all: false,
705        }
706    }
707
708    pub fn strategy(mut self, value: impl Into<String>) -> Self {
709        self.strategy = Some(value.into());
710        self
711    }
712
713    pub fn match_window(mut self, spec: impl Into<String>) -> Self {
714        self.match_window = Some(spec.into());
715        self
716    }
717
718    pub fn match_tab(mut self, spec: impl Into<String>) -> Self {
719        self.match_tab = Some(spec.into());
720        self
721    }
722
723    pub fn all(mut self, value: bool) -> Self {
724        self.all = value;
725        self
726    }
727
728    pub fn build(self) -> Result<KittyMessage, CommandError> {
729        let mut payload = Map::new();
730
731        if let Some(strategy) = self.strategy {
732            payload.insert("strategy".to_string(), serde_json::Value::String(strategy));
733        }
734
735        if let Some(match_window) = self.match_window {
736            payload.insert("match_window".to_string(), serde_json::Value::String(match_window));
737        }
738
739        if let Some(match_tab) = self.match_tab {
740            payload.insert("match_tab".to_string(), serde_json::Value::String(match_tab));
741        }
742
743        if self.all {
744            payload.insert("all".to_string(), serde_json::Value::Bool(true));
745        }
746
747        Ok(CommandBuilder::new("disable-ligatures")
748            .payload(serde_json::Value::Object(payload))
749            .build())
750    }
751}
752
753pub struct SignalChildCommand {
754    signals: Vec<i32>,
755    match_spec: Option<String>,
756}
757
758impl SignalChildCommand {
759    pub fn new(signals: Vec<i32>) -> Self {
760        Self {
761            signals,
762            match_spec: None,
763        }
764    }
765
766    pub fn match_spec(mut self, spec: impl Into<String>) -> Self {
767        self.match_spec = Some(spec.into());
768        self
769    }
770
771    pub fn build(self) -> Result<KittyMessage, CommandError> {
772        let mut payload = Map::new();
773
774        if self.signals.is_empty() {
775            return Err(CommandError::MissingParameter("signals".to_string(), "signal-child".to_string()));
776        }
777
778        payload.insert("signals".to_string(), serde_json::json!(self.signals));
779
780        if let Some(match_spec) = self.match_spec {
781            payload.insert("match".to_string(), serde_json::Value::String(match_spec));
782        }
783
784        Ok(CommandBuilder::new("signal-child")
785            .payload(serde_json::Value::Object(payload))
786            .build())
787    }
788}
789
790#[cfg(test)]
791mod tests {
792    use super::*;
793
794    #[test]
795    fn test_run_basic() {
796        let cmd = RunCommand::new().build();
797        assert!(cmd.is_ok());
798        let msg = cmd.unwrap();
799        assert_eq!(msg.cmd, "run");
800    }
801
802    #[test]
803    fn test_run_with_options() {
804        let cmd = RunCommand::new()
805            .data("test data")
806            .cmdline("bash")
807            .allow_remote_control(true)
808            .build();
809        assert!(cmd.is_ok());
810        let msg = cmd.unwrap();
811        assert_eq!(msg.cmd, "run");
812    }
813
814    #[test]
815    fn test_kitten_basic() {
816        let cmd = KittenCommand::new().build();
817        assert!(cmd.is_ok());
818        let msg = cmd.unwrap();
819        assert_eq!(msg.cmd, "kitten");
820    }
821
822    #[test]
823    fn test_kitten_with_args() {
824        let cmd = KittenCommand::new().args("diff").build();
825        assert!(cmd.is_ok());
826        let msg = cmd.unwrap();
827        assert_eq!(msg.cmd, "kitten");
828    }
829
830    #[test]
831    fn test_launch_basic() {
832        let cmd = LaunchCommand::new().build();
833        assert!(cmd.is_ok());
834        let msg = cmd.unwrap();
835        assert_eq!(msg.cmd, "launch");
836    }
837
838    #[test]
839    fn test_launch_with_options() {
840        let cmd = LaunchCommand::new()
841            .args("bash")
842            .window_title("Test")
843            .cwd("/home")
844            .keep_focus(true)
845            .build();
846        assert!(cmd.is_ok());
847        let msg = cmd.unwrap();
848        assert_eq!(msg.cmd, "launch");
849    }
850
851    #[test]
852    fn test_env_basic() {
853        let mut env_map = Map::new();
854        env_map.insert("PATH".to_string(), serde_json::Value::String("/usr/bin".to_string()));
855        let cmd = EnvCommand::new(env_map).build();
856        assert!(cmd.is_ok());
857        let msg = cmd.unwrap();
858        assert_eq!(msg.cmd, "env");
859    }
860
861    #[test]
862    fn test_env_empty() {
863        let cmd = EnvCommand::new(Map::new()).build();
864        assert!(cmd.is_err());
865        if let Err(CommandError::MissingParameter(field, cmd_name)) = cmd {
866            assert_eq!(field, "env");
867            assert_eq!(cmd_name, "env");
868        } else {
869            panic!("Expected MissingParameter error");
870        }
871    }
872
873    #[test]
874    fn test_set_user_vars_basic() {
875        let cmd = SetUserVarsCommand::new(vec!["var1".to_string(), "var2".to_string()]).build();
876        assert!(cmd.is_ok());
877        let msg = cmd.unwrap();
878        assert_eq!(msg.cmd, "set-user-vars");
879    }
880
881    #[test]
882    fn test_set_user_vars_empty() {
883        let cmd = SetUserVarsCommand::new(vec![]).build();
884        assert!(cmd.is_err());
885        if let Err(CommandError::MissingParameter(field, cmd_name)) = cmd {
886            assert_eq!(field, "var");
887            assert_eq!(cmd_name, "set-user-vars");
888        } else {
889            panic!("Expected MissingParameter error");
890        }
891    }
892
893    #[test]
894    fn test_load_config_basic() {
895        let cmd = LoadConfigCommand::new(vec!["kitty.conf".to_string()]).build();
896        assert!(cmd.is_ok());
897        let msg = cmd.unwrap();
898        assert_eq!(msg.cmd, "load-config");
899    }
900
901    #[test]
902    fn test_load_config_empty() {
903        let cmd = LoadConfigCommand::new(vec![]).build();
904        assert!(cmd.is_err());
905        if let Err(CommandError::MissingParameter(field, cmd_name)) = cmd {
906            assert_eq!(field, "paths");
907            assert_eq!(cmd_name, "load-config");
908        } else {
909            panic!("Expected MissingParameter error");
910        }
911    }
912
913    #[test]
914    fn test_resize_os_window_basic() {
915        let cmd = ResizeOSWindowCommand::new().build();
916        assert!(cmd.is_ok());
917        let msg = cmd.unwrap();
918        assert_eq!(msg.cmd, "resize-os-window");
919    }
920
921    #[test]
922    fn test_resize_os_window_with_options() {
923        let cmd = ResizeOSWindowCommand::new()
924            .width(800)
925            .height(600)
926            .unit("px")
927            .build();
928        assert!(cmd.is_ok());
929        let msg = cmd.unwrap();
930        assert_eq!(msg.cmd, "resize-os-window");
931    }
932
933    #[test]
934    fn test_disable_ligatures_basic() {
935        let cmd = DisableLigaturesCommand::new().build();
936        assert!(cmd.is_ok());
937        let msg = cmd.unwrap();
938        assert_eq!(msg.cmd, "disable-ligatures");
939    }
940
941    #[test]
942    fn test_disable_ligatures_with_options() {
943        let cmd = DisableLigaturesCommand::new()
944            .strategy("never")
945            .all(true)
946            .build();
947        assert!(cmd.is_ok());
948        let msg = cmd.unwrap();
949        assert_eq!(msg.cmd, "disable-ligatures");
950    }
951
952    #[test]
953    fn test_signal_child_basic() {
954        let cmd = SignalChildCommand::new(vec![9, 15]).build();
955        assert!(cmd.is_ok());
956        let msg = cmd.unwrap();
957        assert_eq!(msg.cmd, "signal-child");
958    }
959
960    #[test]
961    fn test_signal_child_empty() {
962        let cmd = SignalChildCommand::new(vec![]).build();
963        assert!(cmd.is_err());
964        if let Err(CommandError::MissingParameter(field, cmd_name)) = cmd {
965            assert_eq!(field, "signals");
966            assert_eq!(cmd_name, "signal-child");
967        } else {
968            panic!("Expected MissingParameter error");
969        }
970    }
971}