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