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(
55 "title".to_string(),
56 "set-tab-title".to_string(),
57 ));
58 }
59
60 payload.insert("title".to_string(), serde_json::Value::String(self.title));
61
62 if let Some(match_spec) = self.match_spec {
63 payload.insert("match".to_string(), serde_json::Value::String(match_spec));
64 }
65
66 Ok(CommandBuilder::new("set-tab-title")
67 .payload(serde_json::Value::Object(payload))
68 .build())
69 }
70}
71
72pub struct CloseTabCommand {
73 match_spec: Option<String>,
74 self_tab: bool,
75 ignore_no_match: bool,
76}
77
78impl CloseTabCommand {
79 pub fn new() -> Self {
80 Self {
81 match_spec: None,
82 self_tab: false,
83 ignore_no_match: false,
84 }
85 }
86
87 pub fn match_spec(mut self, spec: impl Into<String>) -> Self {
88 self.match_spec = Some(spec.into());
89 self
90 }
91
92 pub fn self_tab(mut self, value: bool) -> Self {
93 self.self_tab = value;
94 self
95 }
96
97 pub fn ignore_no_match(mut self, value: bool) -> Self {
98 self.ignore_no_match = value;
99 self
100 }
101
102 pub fn build(self) -> Result<KittyMessage, CommandError> {
103 let mut payload = serde_json::Map::new();
104
105 if let Some(match_spec) = self.match_spec {
106 payload.insert("match".to_string(), serde_json::Value::String(match_spec));
107 }
108
109 if self.self_tab {
110 payload.insert("self".to_string(), serde_json::Value::Bool(true));
111 }
112
113 if self.ignore_no_match {
114 payload.insert("ignore_no_match".to_string(), serde_json::Value::Bool(true));
115 }
116
117 Ok(CommandBuilder::new("close-tab")
118 .payload(serde_json::Value::Object(payload))
119 .build())
120 }
121}
122
123pub struct DetachTabCommand {
124 match_spec: Option<String>,
125 target_tab: Option<String>,
126 self_tab: bool,
127}
128
129impl DetachTabCommand {
130 pub fn new() -> Self {
131 Self {
132 match_spec: None,
133 target_tab: None,
134 self_tab: false,
135 }
136 }
137
138 pub fn match_spec(mut self, spec: impl Into<String>) -> Self {
139 self.match_spec = Some(spec.into());
140 self
141 }
142
143 pub fn target_tab(mut self, spec: impl Into<String>) -> Self {
144 self.target_tab = Some(spec.into());
145 self
146 }
147
148 pub fn self_tab(mut self, value: bool) -> Self {
149 self.self_tab = value;
150 self
151 }
152
153 pub fn build(self) -> Result<KittyMessage, CommandError> {
154 let mut payload = serde_json::Map::new();
155
156 if let Some(match_spec) = self.match_spec {
157 payload.insert("match".to_string(), serde_json::Value::String(match_spec));
158 }
159
160 if let Some(target_tab) = self.target_tab {
161 payload.insert(
162 "target_tab".to_string(),
163 serde_json::Value::String(target_tab),
164 );
165 }
166
167 if self.self_tab {
168 payload.insert("self".to_string(), serde_json::Value::Bool(true));
169 }
170
171 Ok(CommandBuilder::new("detach-tab")
172 .payload(serde_json::Value::Object(payload))
173 .build())
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 #[test]
182 fn test_focus_tab_basic() {
183 let cmd = FocusTabCommand::new().build();
184 assert!(cmd.is_ok());
185 let msg = cmd.unwrap();
186 assert_eq!(msg.cmd, "focus-tab");
187 }
188
189 #[test]
190 fn test_focus_tab_with_match() {
191 let cmd = FocusTabCommand::new().match_spec("id:0").build();
192 assert!(cmd.is_ok());
193 let msg = cmd.unwrap();
194 assert_eq!(msg.cmd, "focus-tab");
195 assert!(msg.payload.is_some());
196 }
197
198 #[test]
199 fn test_set_tab_title() {
200 let cmd = SetTabTitleCommand::new("My Tab").build();
201 assert!(cmd.is_ok());
202 let msg = cmd.unwrap();
203 assert_eq!(msg.cmd, "set-tab-title");
204 assert!(msg.payload.is_some());
205 }
206
207 #[test]
208 fn test_set_tab_title_empty() {
209 let cmd = SetTabTitleCommand::new("").build();
210 assert!(cmd.is_err());
211 if let Err(CommandError::MissingParameter(field, cmd_name)) = cmd {
212 assert_eq!(field, "title");
213 assert_eq!(cmd_name, "set-tab-title");
214 } else {
215 panic!("Expected MissingParameter error");
216 }
217 }
218
219 #[test]
220 fn test_set_tab_title_with_match() {
221 let cmd = SetTabTitleCommand::new("New Title")
222 .match_spec("id:1")
223 .build();
224 assert!(cmd.is_ok());
225 let msg = cmd.unwrap();
226 assert_eq!(msg.cmd, "set-tab-title");
227 }
228
229 #[test]
230 fn test_close_tab_basic() {
231 let cmd = CloseTabCommand::new().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_with_match() {
239 let cmd = CloseTabCommand::new().match_spec("id:2").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_self() {
247 let cmd = CloseTabCommand::new().self_tab(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_close_tab_ignore_no_match() {
255 let cmd = CloseTabCommand::new().ignore_no_match(true).build();
256 assert!(cmd.is_ok());
257 let msg = cmd.unwrap();
258 assert_eq!(msg.cmd, "close-tab");
259 }
260
261 #[test]
262 fn test_detach_tab_basic() {
263 let cmd = DetachTabCommand::new().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_match() {
271 let cmd = DetachTabCommand::new().match_spec("id:0").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_with_target_tab() {
279 let cmd = DetachTabCommand::new().target_tab("id:1").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_self() {
287 let cmd = DetachTabCommand::new().self_tab(true).build();
288 assert!(cmd.is_ok());
289 let msg = cmd.unwrap();
290 assert_eq!(msg.cmd, "detach-tab");
291 }
292
293 #[test]
294 fn test_detach_tab_all_options() {
295 let cmd = DetachTabCommand::new()
296 .match_spec("id:0")
297 .target_tab("id:1")
298 .self_tab(true)
299 .build();
300 assert!(cmd.is_ok());
301 let msg = cmd.unwrap();
302 assert_eq!(msg.cmd, "detach-tab");
303 }
304}