Skip to main content

rmux_proto/request/
window.rs

1use serde::de::{self, MapAccess, SeqAccess, Visitor};
2use serde::{Deserialize, Deserializer, Serialize};
3use std::path::PathBuf;
4
5use crate::{ProcessCommand, SessionName, WindowTarget};
6
7use super::compat::compat_next_element;
8
9/// Request payload for `new-window`.
10#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
11pub struct NewWindowRequest {
12    /// The exact target session name.
13    pub target: SessionName,
14    /// The optional explicit window name.
15    pub name: Option<String>,
16    /// Whether the newly created window should remain inactive.
17    pub detached: bool,
18    /// Optional per-spawn environment overrides in `NAME=VALUE` form.
19    #[serde(default)]
20    pub environment: Option<Vec<String>>,
21    /// Optional shell command argv. A single argument is executed via `$SHELL -c`.
22    #[serde(default)]
23    pub command: Option<Vec<String>>,
24    /// Optional working-directory override.
25    #[serde(default)]
26    pub start_directory: Option<PathBuf>,
27    /// Optional destination window index from `new-window -t session:index`.
28    #[serde(default)]
29    pub target_window_index: Option<u32>,
30    /// Whether an occupied destination index should be opened by shifting windows upward.
31    #[serde(default)]
32    pub insert_at_target: bool,
33    /// Explicit process launch mode for the new window's initial pane.
34    #[serde(default)]
35    pub process_command: Option<ProcessCommand>,
36}
37
38/// Request payload for `kill-window`.
39#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40pub struct KillWindowRequest {
41    /// The exact target window.
42    pub target: WindowTarget,
43    /// Whether all other windows in the session should be removed instead.
44    pub kill_all_others: bool,
45}
46
47/// Request payload for `select-window`.
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
49pub struct SelectWindowRequest {
50    /// The exact target window.
51    pub target: WindowTarget,
52}
53
54/// Request payload for `rename-window`.
55#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
56pub struct RenameWindowRequest {
57    /// The exact target window.
58    pub target: WindowTarget,
59    /// The new window name.
60    pub name: String,
61}
62
63/// Request payload for `next-window`.
64#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
65pub struct NextWindowRequest {
66    /// The exact target session name.
67    pub target: SessionName,
68    /// Whether only alerted windows should be considered.
69    #[serde(default)]
70    pub alerts_only: bool,
71}
72
73/// Request payload for `previous-window`.
74#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
75pub struct PreviousWindowRequest {
76    /// The exact target session name.
77    pub target: SessionName,
78    /// Whether only alerted windows should be considered.
79    #[serde(default)]
80    pub alerts_only: bool,
81}
82
83/// Request payload for `last-window`.
84#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
85pub struct LastWindowRequest {
86    /// The exact target session name.
87    pub target: SessionName,
88}
89
90/// Request payload for `list-windows`.
91#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
92pub struct ListWindowsRequest {
93    /// The exact target session name.
94    pub target: SessionName,
95    /// An optional server-side compatibility format template.
96    pub format: Option<String>,
97    /// Optional filter expression.
98    #[serde(default)]
99    pub filter: Option<String>,
100    /// Optional sort-order token.
101    #[serde(default)]
102    pub sort_order: Option<String>,
103    /// Whether the selected sort order should be reversed.
104    #[serde(default)]
105    pub reversed: bool,
106}
107
108/// Request payload for `link-window`.
109#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
110pub struct LinkWindowRequest {
111    /// The source window slot.
112    pub source: WindowTarget,
113    /// The destination window slot.
114    pub target: WindowTarget,
115    /// Whether to insert after the target slot (`-a`).
116    #[serde(default)]
117    pub after: bool,
118    /// Whether to insert before the target slot (`-b`).
119    #[serde(default)]
120    pub before: bool,
121    /// Whether an occupied destination should be replaced (`-k`).
122    #[serde(default)]
123    pub kill_destination: bool,
124    /// Whether the destination session should keep its current active window (`-d`).
125    #[serde(default)]
126    pub detached: bool,
127}
128
129/// Target forms accepted by `move-window`.
130#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
131pub enum MoveWindowTarget {
132    /// Applies to the addressed session during `move-window -r`.
133    Session(SessionName),
134    /// Applies to the addressed destination window slot.
135    Window(WindowTarget),
136}
137
138/// Request payload for `move-window`.
139#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
140pub struct MoveWindowRequest {
141    /// The optional source window being moved when not reindexing.
142    pub source: Option<WindowTarget>,
143    /// The destination window slot or reindex target session.
144    pub target: MoveWindowTarget,
145    /// Whether the session should be reindexed instead of moving one window.
146    pub renumber: bool,
147    /// Whether an occupied destination should be replaced.
148    pub kill_destination: bool,
149    /// Whether the destination session should keep its current active window.
150    pub detached: bool,
151    /// Whether to insert after the target slot (`-a`).
152    #[serde(default)]
153    pub after: bool,
154    /// Whether to insert before the target slot (`-b`).
155    #[serde(default)]
156    pub before: bool,
157}
158
159/// Request payload for `swap-window`.
160#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
161pub struct SwapWindowRequest {
162    /// The source window slot.
163    pub source: WindowTarget,
164    /// The destination window slot.
165    pub target: WindowTarget,
166    /// Whether the swapped destination slots should become active after the swap.
167    pub detached: bool,
168}
169
170/// The supported pane rotation directions for `rotate-window`.
171#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
172pub enum RotateWindowDirection {
173    /// Move the last pane to the head.
174    Down,
175    /// Move the first pane to the tail.
176    Up,
177}
178
179/// Request payload for `rotate-window`.
180#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
181pub struct RotateWindowRequest {
182    /// The addressed window.
183    pub target: WindowTarget,
184    /// The requested rotation direction.
185    pub direction: RotateWindowDirection,
186    /// Whether to save and restore zoom state around the rotation (`-Z`).
187    #[serde(default)]
188    pub restore_zoom: bool,
189}
190
191/// Request payload for `resize-window`.
192#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
193pub struct ResizeWindowRequest {
194    /// The addressed window.
195    pub target: WindowTarget,
196    /// Optional explicit width (`-x`).
197    pub width: Option<u16>,
198    /// Optional explicit height (`-y`).
199    pub height: Option<u16>,
200    /// Relative adjustment (from `-D`, `-U`, `-L`, `-R`).
201    #[serde(default)]
202    pub adjustment: Option<ResizeWindowAdjustment>,
203}
204
205/// Directional relative-size adjustment for `resize-window`.
206#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
207pub enum ResizeWindowAdjustment {
208    /// Shrink height (`-U`).
209    Up(u16),
210    /// Grow height (`-D`).
211    Down(u16),
212    /// Shrink width (`-L`).
213    Left(u16),
214    /// Grow width (`-R`).
215    Right(u16),
216    /// Resize to the largest attached session containing the window (`-A`).
217    LargestLinkedSession,
218    /// Resize to the smallest attached session containing the window (`-a`).
219    SmallestLinkedSession,
220}
221
222/// Request payload for `respawn-window`.
223#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
224pub struct RespawnWindowRequest {
225    /// The addressed window.
226    pub target: WindowTarget,
227    /// Whether to kill existing panes even when they are still running (`-k`).
228    #[serde(default)]
229    pub kill: bool,
230    /// Optional per-spawn environment overrides in `NAME=VALUE` form.
231    #[serde(default)]
232    pub environment: Option<Vec<String>>,
233    /// Optional shell command argv. A single argument is executed via `$SHELL -c`.
234    #[serde(default)]
235    pub command: Option<Vec<String>>,
236    /// Optional working-directory override.
237    #[serde(default)]
238    pub start_directory: Option<PathBuf>,
239}
240
241impl<'de> Deserialize<'de> for NewWindowRequest {
242    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
243    where
244        D: Deserializer<'de>,
245    {
246        deserializer.deserialize_struct(
247            "NewWindowRequest",
248            &[
249                "target",
250                "name",
251                "detached",
252                "environment",
253                "command",
254                "start_directory",
255                "target_window_index",
256                "insert_at_target",
257                "process_command",
258            ],
259            NewWindowRequestVisitor,
260        )
261    }
262}
263
264impl<'de> Deserialize<'de> for RespawnWindowRequest {
265    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
266    where
267        D: Deserializer<'de>,
268    {
269        deserializer.deserialize_struct(
270            "RespawnWindowRequest",
271            &[
272                "target",
273                "kill",
274                "environment",
275                "command",
276                "start_directory",
277            ],
278            RespawnWindowRequestVisitor,
279        )
280    }
281}
282
283impl<'de> Deserialize<'de> for MoveWindowRequest {
284    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
285    where
286        D: Deserializer<'de>,
287    {
288        deserializer.deserialize_struct(
289            "MoveWindowRequest",
290            &[
291                "source",
292                "target",
293                "renumber",
294                "kill_destination",
295                "detached",
296                "after",
297                "before",
298            ],
299            MoveWindowRequestVisitor,
300        )
301    }
302}
303
304struct NewWindowRequestVisitor;
305
306struct MoveWindowRequestVisitor;
307
308impl<'de> Visitor<'de> for NewWindowRequestVisitor {
309    type Value = NewWindowRequest;
310
311    fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
312        formatter.write_str("a new-window request")
313    }
314
315    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
316    where
317        A: SeqAccess<'de>,
318    {
319        let target = seq
320            .next_element()?
321            .ok_or_else(|| de::Error::invalid_length(0, &self))?;
322        let name = seq
323            .next_element()?
324            .ok_or_else(|| de::Error::invalid_length(1, &self))?;
325        let detached = seq
326            .next_element()?
327            .ok_or_else(|| de::Error::invalid_length(2, &self))?;
328        let environment = seq
329            .next_element()?
330            .ok_or_else(|| de::Error::invalid_length(3, &self))?;
331        let command = compat_next_element(&mut seq)?;
332        let start_directory = compat_next_element(&mut seq)?;
333        let target_window_index = compat_next_element(&mut seq)?;
334        let insert_at_target = compat_next_element(&mut seq)?;
335        let process_command = compat_next_element(&mut seq)?;
336
337        Ok(NewWindowRequest {
338            target,
339            name,
340            detached,
341            environment,
342            command,
343            start_directory,
344            target_window_index,
345            insert_at_target,
346            process_command,
347        })
348    }
349
350    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
351    where
352        A: MapAccess<'de>,
353    {
354        let mut target = None;
355        let mut name = None;
356        let mut detached = None;
357        let mut environment = None;
358        let mut command = None;
359        let mut process_command = None;
360        let mut start_directory = None;
361        let mut target_window_index = None;
362        let mut insert_at_target = None;
363
364        while let Some(key) = map.next_key::<String>()? {
365            match key.as_str() {
366                "target" => target = Some(map.next_value()?),
367                "name" => name = Some(map.next_value()?),
368                "detached" => detached = Some(map.next_value()?),
369                "environment" => environment = Some(map.next_value()?),
370                "command" => command = Some(map.next_value()?),
371                "process_command" => process_command = Some(map.next_value()?),
372                "start_directory" => start_directory = Some(map.next_value()?),
373                "target_window_index" => target_window_index = Some(map.next_value()?),
374                "insert_at_target" => insert_at_target = Some(map.next_value()?),
375                _ => {
376                    let _: de::IgnoredAny = map.next_value()?;
377                }
378            }
379        }
380
381        Ok(NewWindowRequest {
382            target: target.ok_or_else(|| de::Error::missing_field("target"))?,
383            name: name.unwrap_or_default(),
384            detached: detached.ok_or_else(|| de::Error::missing_field("detached"))?,
385            environment: environment.unwrap_or_default(),
386            command: command.unwrap_or_default(),
387            process_command: process_command.unwrap_or_default(),
388            start_directory: start_directory.unwrap_or_default(),
389            target_window_index: target_window_index.unwrap_or_default(),
390            insert_at_target: insert_at_target.unwrap_or_default(),
391        })
392    }
393}
394
395impl<'de> Visitor<'de> for MoveWindowRequestVisitor {
396    type Value = MoveWindowRequest;
397
398    fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
399        formatter.write_str("a move-window request")
400    }
401
402    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
403    where
404        A: SeqAccess<'de>,
405    {
406        let source = seq
407            .next_element()?
408            .ok_or_else(|| de::Error::invalid_length(0, &self))?;
409        let target = seq
410            .next_element()?
411            .ok_or_else(|| de::Error::invalid_length(1, &self))?;
412        let renumber = seq
413            .next_element()?
414            .ok_or_else(|| de::Error::invalid_length(2, &self))?;
415        let kill_destination = seq
416            .next_element()?
417            .ok_or_else(|| de::Error::invalid_length(3, &self))?;
418        let detached = seq
419            .next_element()?
420            .ok_or_else(|| de::Error::invalid_length(4, &self))?;
421        let after = compat_next_element(&mut seq)?;
422        let before = compat_next_element(&mut seq)?;
423
424        Ok(MoveWindowRequest {
425            source,
426            target,
427            renumber,
428            kill_destination,
429            detached,
430            after,
431            before,
432        })
433    }
434
435    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
436    where
437        A: MapAccess<'de>,
438    {
439        let mut source = None;
440        let mut target = None;
441        let mut renumber = None;
442        let mut kill_destination = None;
443        let mut detached = None;
444        let mut after = None;
445        let mut before = None;
446
447        while let Some(key) = map.next_key::<String>()? {
448            match key.as_str() {
449                "source" => source = Some(map.next_value()?),
450                "target" => target = Some(map.next_value()?),
451                "renumber" => renumber = Some(map.next_value()?),
452                "kill_destination" => kill_destination = Some(map.next_value()?),
453                "detached" => detached = Some(map.next_value()?),
454                "after" => after = Some(map.next_value()?),
455                "before" => before = Some(map.next_value()?),
456                _ => {
457                    let _: de::IgnoredAny = map.next_value()?;
458                }
459            }
460        }
461
462        Ok(MoveWindowRequest {
463            source: source.unwrap_or_default(),
464            target: target.ok_or_else(|| de::Error::missing_field("target"))?,
465            renumber: renumber.ok_or_else(|| de::Error::missing_field("renumber"))?,
466            kill_destination: kill_destination
467                .ok_or_else(|| de::Error::missing_field("kill_destination"))?,
468            detached: detached.ok_or_else(|| de::Error::missing_field("detached"))?,
469            after: after.unwrap_or_default(),
470            before: before.unwrap_or_default(),
471        })
472    }
473}
474
475struct RespawnWindowRequestVisitor;
476
477impl<'de> Visitor<'de> for RespawnWindowRequestVisitor {
478    type Value = RespawnWindowRequest;
479
480    fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
481        formatter.write_str("a respawn-window request")
482    }
483
484    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
485    where
486        A: SeqAccess<'de>,
487    {
488        let target = seq
489            .next_element()?
490            .ok_or_else(|| de::Error::invalid_length(0, &self))?;
491        let kill = seq
492            .next_element()?
493            .ok_or_else(|| de::Error::invalid_length(1, &self))?;
494        let environment = seq
495            .next_element()?
496            .ok_or_else(|| de::Error::invalid_length(2, &self))?;
497        let command = compat_next_element(&mut seq)?;
498        let start_directory = compat_next_element(&mut seq)?;
499
500        Ok(RespawnWindowRequest {
501            target,
502            kill,
503            environment,
504            command,
505            start_directory,
506        })
507    }
508
509    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
510    where
511        A: MapAccess<'de>,
512    {
513        let mut target = None;
514        let mut kill = None;
515        let mut environment = None;
516        let mut command = None;
517        let mut start_directory = None;
518
519        while let Some(key) = map.next_key::<String>()? {
520            match key.as_str() {
521                "target" => target = Some(map.next_value()?),
522                "kill" => kill = Some(map.next_value()?),
523                "environment" => environment = Some(map.next_value()?),
524                "command" => command = Some(map.next_value()?),
525                "start_directory" => start_directory = Some(map.next_value()?),
526                _ => {
527                    let _: de::IgnoredAny = map.next_value()?;
528                }
529            }
530        }
531
532        Ok(RespawnWindowRequest {
533            target: target.ok_or_else(|| de::Error::missing_field("target"))?,
534            kill: kill.unwrap_or_default(),
535            environment: environment.unwrap_or_default(),
536            command: command.unwrap_or_default(),
537            start_directory: start_directory.unwrap_or_default(),
538        })
539    }
540}