Skip to main content

kitty_rc/commands/
tab.rs

1use crate::command::CommandBuilder;
2use crate::error::CommandError;
3use crate::protocol::KittyMessage;
4use bon::Builder;
5use serde::Serialize;
6
7fn is_false(v: &bool) -> bool {
8    !v
9}
10
11#[derive(Builder, Serialize)]
12pub struct FocusTabCommand {
13    #[serde(skip_serializing_if = "Option::is_none", rename = "match", default)]
14    match_spec: Option<String>,
15}
16
17impl FocusTabCommand {
18    pub fn to_message(self) -> Result<KittyMessage, CommandError> {
19        let payload =
20            serde_json::to_value(self).map_err(|e| CommandError::ValidationError(e.to_string()))?;
21
22        Ok(CommandBuilder::new("focus-tab").payload(payload).build())
23    }
24}
25
26#[derive(Builder, Serialize)]
27pub struct SetTabTitleCommand {
28    title: String,
29
30    #[serde(skip_serializing_if = "Option::is_none", rename = "match", default)]
31    match_spec: Option<String>,
32}
33
34impl SetTabTitleCommand {
35    pub fn to_message(self) -> Result<KittyMessage, CommandError> {
36        if self.title.is_empty() {
37            return Err(CommandError::MissingParameter(
38                "title".to_string(),
39                "set-tab-title".to_string(),
40            ));
41        }
42
43        let payload =
44            serde_json::to_value(self).map_err(|e| CommandError::ValidationError(e.to_string()))?;
45
46        Ok(CommandBuilder::new("set-tab-title")
47            .payload(payload)
48            .build())
49    }
50}
51
52#[derive(Builder, Serialize)]
53pub struct CloseTabCommand {
54    #[serde(skip_serializing_if = "Option::is_none", rename = "match", default)]
55    match_spec: Option<String>,
56
57    #[builder(default = false)]
58    #[serde(skip_serializing_if = "is_false", rename = "self")]
59    self_tab: bool,
60
61    #[builder(default = false)]
62    #[serde(skip_serializing_if = "is_false")]
63    ignore_no_match: bool,
64}
65
66impl CloseTabCommand {
67    pub fn to_message(self) -> Result<KittyMessage, CommandError> {
68        let payload =
69            serde_json::to_value(self).map_err(|e| CommandError::ValidationError(e.to_string()))?;
70
71        Ok(CommandBuilder::new("close-tab").payload(payload).build())
72    }
73}
74
75#[derive(Builder, Serialize)]
76pub struct DetachTabCommand {
77    #[serde(skip_serializing_if = "Option::is_none", rename = "match", default)]
78    match_spec: Option<String>,
79
80    #[serde(skip_serializing_if = "Option::is_none", default)]
81    target_tab: Option<String>,
82
83    #[builder(default = false)]
84    #[serde(skip_serializing_if = "is_false", rename = "self")]
85    self_tab: bool,
86}
87
88impl DetachTabCommand {
89    pub fn to_message(self) -> Result<KittyMessage, CommandError> {
90        let payload =
91            serde_json::to_value(self).map_err(|e| CommandError::ValidationError(e.to_string()))?;
92
93        Ok(CommandBuilder::new("detach-tab").payload(payload).build())
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn test_focus_tab_basic() {
103        let cmd = FocusTabCommand::builder().build().to_message();
104        assert!(cmd.is_ok());
105        let msg = cmd.unwrap();
106        assert_eq!(msg.cmd, "focus-tab");
107    }
108
109    #[test]
110    fn test_focus_tab_with_match() {
111        let cmd = FocusTabCommand::builder()
112            .match_spec("id:0".to_string())
113            .build()
114            .to_message();
115        assert!(cmd.is_ok());
116        let msg = cmd.unwrap();
117        assert_eq!(msg.cmd, "focus-tab");
118        assert!(msg.payload.is_some());
119    }
120
121    #[test]
122    fn test_set_tab_title() {
123        let cmd = SetTabTitleCommand::builder()
124            .title("My Tab".to_string())
125            .build()
126            .to_message();
127        assert!(cmd.is_ok());
128        let msg = cmd.unwrap();
129        assert_eq!(msg.cmd, "set-tab-title");
130        assert!(msg.payload.is_some());
131    }
132
133    #[test]
134    fn test_set_tab_title_empty() {
135        let cmd = SetTabTitleCommand::builder()
136            .title("".to_string())
137            .build()
138            .to_message();
139        assert!(cmd.is_err());
140        if let Err(CommandError::MissingParameter(field, cmd_name)) = cmd {
141            assert_eq!(field, "title");
142            assert_eq!(cmd_name, "set-tab-title");
143        } else {
144            panic!("Expected MissingParameter error");
145        }
146    }
147
148    #[test]
149    fn test_set_tab_title_with_match() {
150        let cmd = SetTabTitleCommand::builder()
151            .title("New Title".to_string())
152            .match_spec("id:1".to_string())
153            .build()
154            .to_message();
155        assert!(cmd.is_ok());
156        let msg = cmd.unwrap();
157        assert_eq!(msg.cmd, "set-tab-title");
158    }
159
160    #[test]
161    fn test_close_tab_basic() {
162        let cmd = CloseTabCommand::builder().build().to_message();
163        assert!(cmd.is_ok());
164        let msg = cmd.unwrap();
165        assert_eq!(msg.cmd, "close-tab");
166    }
167
168    #[test]
169    fn test_close_tab_with_match() {
170        let cmd = CloseTabCommand::builder()
171            .match_spec("id:2".to_string())
172            .build()
173            .to_message();
174        assert!(cmd.is_ok());
175        let msg = cmd.unwrap();
176        assert_eq!(msg.cmd, "close-tab");
177    }
178
179    #[test]
180    fn test_close_tab_self() {
181        let cmd = CloseTabCommand::builder()
182            .self_tab(true)
183            .build()
184            .to_message();
185        assert!(cmd.is_ok());
186        let msg = cmd.unwrap();
187        assert_eq!(msg.cmd, "close-tab");
188    }
189
190    #[test]
191    fn test_close_tab_ignore_no_match() {
192        let cmd = CloseTabCommand::builder()
193            .ignore_no_match(true)
194            .build()
195            .to_message();
196        assert!(cmd.is_ok());
197        let msg = cmd.unwrap();
198        assert_eq!(msg.cmd, "close-tab");
199    }
200
201    #[test]
202    fn test_detach_tab_basic() {
203        let cmd = DetachTabCommand::builder().build().to_message();
204        assert!(cmd.is_ok());
205        let msg = cmd.unwrap();
206        assert_eq!(msg.cmd, "detach-tab");
207    }
208
209    #[test]
210    fn test_detach_tab_with_match() {
211        let cmd = DetachTabCommand::builder()
212            .match_spec("id:0".to_string())
213            .build()
214            .to_message();
215        assert!(cmd.is_ok());
216        let msg = cmd.unwrap();
217        assert_eq!(msg.cmd, "detach-tab");
218    }
219
220    #[test]
221    fn test_detach_tab_with_target_tab() {
222        let cmd = DetachTabCommand::builder()
223            .target_tab("id:1".to_string())
224            .build()
225            .to_message();
226        assert!(cmd.is_ok());
227        let msg = cmd.unwrap();
228        assert_eq!(msg.cmd, "detach-tab");
229    }
230
231    #[test]
232    fn test_detach_tab_self() {
233        let cmd = DetachTabCommand::builder()
234            .self_tab(true)
235            .build()
236            .to_message();
237        assert!(cmd.is_ok());
238        let msg = cmd.unwrap();
239        assert_eq!(msg.cmd, "detach-tab");
240    }
241
242    #[test]
243    fn test_detach_tab_all_options() {
244        let cmd = DetachTabCommand::builder()
245            .match_spec("id:0".to_string())
246            .target_tab("id:1".to_string())
247            .self_tab(true)
248            .build()
249            .to_message();
250        assert!(cmd.is_ok());
251        let msg = cmd.unwrap();
252        assert_eq!(msg.cmd, "detach-tab");
253    }
254}