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