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