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::{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}
34
35/// Request payload for `kill-window`.
36#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
37pub struct KillWindowRequest {
38    /// The exact target window.
39    pub target: WindowTarget,
40    /// Whether all other windows in the session should be removed instead.
41    pub kill_all_others: bool,
42}
43
44/// Request payload for `select-window`.
45#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
46pub struct SelectWindowRequest {
47    /// The exact target window.
48    pub target: WindowTarget,
49}
50
51/// Request payload for `rename-window`.
52#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
53pub struct RenameWindowRequest {
54    /// The exact target window.
55    pub target: WindowTarget,
56    /// The new window name.
57    pub name: String,
58}
59
60/// Request payload for `next-window`.
61#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
62pub struct NextWindowRequest {
63    /// The exact target session name.
64    pub target: SessionName,
65    /// Whether only alerted windows should be considered.
66    #[serde(default)]
67    pub alerts_only: bool,
68}
69
70/// Request payload for `previous-window`.
71#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
72pub struct PreviousWindowRequest {
73    /// The exact target session name.
74    pub target: SessionName,
75    /// Whether only alerted windows should be considered.
76    #[serde(default)]
77    pub alerts_only: bool,
78}
79
80/// Request payload for `last-window`.
81#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
82pub struct LastWindowRequest {
83    /// The exact target session name.
84    pub target: SessionName,
85}
86
87/// Request payload for `list-windows`.
88#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
89pub struct ListWindowsRequest {
90    /// The exact target session name.
91    pub target: SessionName,
92    /// An optional server-side compatibility format template.
93    pub format: Option<String>,
94}
95
96/// Request payload for `link-window`.
97#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
98pub struct LinkWindowRequest {
99    /// The source window slot.
100    pub source: WindowTarget,
101    /// The destination window slot.
102    pub target: WindowTarget,
103    /// Whether to insert after the target slot (`-a`).
104    #[serde(default)]
105    pub after: bool,
106    /// Whether to insert before the target slot (`-b`).
107    #[serde(default)]
108    pub before: bool,
109    /// Whether an occupied destination should be replaced (`-k`).
110    #[serde(default)]
111    pub kill_destination: bool,
112    /// Whether the destination session should keep its current active window (`-d`).
113    #[serde(default)]
114    pub detached: bool,
115}
116
117/// Target forms accepted by `move-window`.
118#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
119pub enum MoveWindowTarget {
120    /// Applies to the addressed session during `move-window -r`.
121    Session(SessionName),
122    /// Applies to the addressed destination window slot.
123    Window(WindowTarget),
124}
125
126/// Request payload for `move-window`.
127#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
128pub struct MoveWindowRequest {
129    /// The optional source window being moved when not reindexing.
130    pub source: Option<WindowTarget>,
131    /// The destination window slot or reindex target session.
132    pub target: MoveWindowTarget,
133    /// Whether the session should be reindexed instead of moving one window.
134    pub renumber: bool,
135    /// Whether an occupied destination should be replaced.
136    pub kill_destination: bool,
137    /// Whether the destination session should keep its current active window.
138    pub detached: bool,
139}
140
141/// Request payload for `swap-window`.
142#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
143pub struct SwapWindowRequest {
144    /// The source window slot.
145    pub source: WindowTarget,
146    /// The destination window slot.
147    pub target: WindowTarget,
148    /// Whether the swapped destination slots should become active after the swap.
149    pub detached: bool,
150}
151
152/// The supported pane rotation directions for `rotate-window`.
153#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
154pub enum RotateWindowDirection {
155    /// Move the last pane to the head.
156    Down,
157    /// Move the first pane to the tail.
158    Up,
159}
160
161/// Request payload for `rotate-window`.
162#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
163pub struct RotateWindowRequest {
164    /// The addressed window.
165    pub target: WindowTarget,
166    /// The requested rotation direction.
167    pub direction: RotateWindowDirection,
168    /// Whether to save and restore zoom state around the rotation (`-Z`).
169    #[serde(default)]
170    pub restore_zoom: bool,
171}
172
173/// Request payload for `resize-window`.
174#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
175pub struct ResizeWindowRequest {
176    /// The addressed window.
177    pub target: WindowTarget,
178    /// Optional explicit width (`-x`).
179    pub width: Option<u16>,
180    /// Optional explicit height (`-y`).
181    pub height: Option<u16>,
182    /// Relative adjustment (from `-D`, `-U`, `-L`, `-R`).
183    #[serde(default)]
184    pub adjustment: Option<ResizeWindowAdjustment>,
185}
186
187/// Directional relative-size adjustment for `resize-window`.
188#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
189pub enum ResizeWindowAdjustment {
190    /// Shrink height (`-U`).
191    Up(u16),
192    /// Grow height (`-D`).
193    Down(u16),
194    /// Shrink width (`-L`).
195    Left(u16),
196    /// Grow width (`-R`).
197    Right(u16),
198}
199
200/// Request payload for `respawn-window`.
201#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
202pub struct RespawnWindowRequest {
203    /// The addressed window.
204    pub target: WindowTarget,
205    /// Whether to kill existing panes even when they are still running (`-k`).
206    #[serde(default)]
207    pub kill: bool,
208    /// Optional per-spawn environment overrides in `NAME=VALUE` form.
209    #[serde(default)]
210    pub environment: Option<Vec<String>>,
211    /// Optional shell command argv. A single argument is executed via `$SHELL -c`.
212    #[serde(default)]
213    pub command: Option<Vec<String>>,
214    /// Optional working-directory override.
215    #[serde(default)]
216    pub start_directory: Option<PathBuf>,
217}
218
219impl<'de> Deserialize<'de> for NewWindowRequest {
220    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
221    where
222        D: Deserializer<'de>,
223    {
224        deserializer.deserialize_struct(
225            "NewWindowRequest",
226            &[
227                "target",
228                "name",
229                "detached",
230                "environment",
231                "command",
232                "start_directory",
233                "target_window_index",
234                "insert_at_target",
235            ],
236            NewWindowRequestVisitor,
237        )
238    }
239}
240
241impl<'de> Deserialize<'de> for RespawnWindowRequest {
242    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
243    where
244        D: Deserializer<'de>,
245    {
246        deserializer.deserialize_struct(
247            "RespawnWindowRequest",
248            &[
249                "target",
250                "kill",
251                "environment",
252                "command",
253                "start_directory",
254            ],
255            RespawnWindowRequestVisitor,
256        )
257    }
258}
259
260struct NewWindowRequestVisitor;
261
262impl<'de> Visitor<'de> for NewWindowRequestVisitor {
263    type Value = NewWindowRequest;
264
265    fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
266        formatter.write_str("a new-window request")
267    }
268
269    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
270    where
271        A: SeqAccess<'de>,
272    {
273        let target = seq
274            .next_element()?
275            .ok_or_else(|| de::Error::invalid_length(0, &self))?;
276        let name = seq
277            .next_element()?
278            .ok_or_else(|| de::Error::invalid_length(1, &self))?;
279        let detached = seq
280            .next_element()?
281            .ok_or_else(|| de::Error::invalid_length(2, &self))?;
282        let environment = seq
283            .next_element()?
284            .ok_or_else(|| de::Error::invalid_length(3, &self))?;
285        let command = compat_next_element(&mut seq)?;
286        let start_directory = compat_next_element(&mut seq)?;
287        let target_window_index = compat_next_element(&mut seq)?;
288        let insert_at_target = compat_next_element(&mut seq)?;
289
290        Ok(NewWindowRequest {
291            target,
292            name,
293            detached,
294            environment,
295            command,
296            start_directory,
297            target_window_index,
298            insert_at_target,
299        })
300    }
301
302    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
303    where
304        A: MapAccess<'de>,
305    {
306        let mut target = None;
307        let mut name = None;
308        let mut detached = None;
309        let mut environment = None;
310        let mut command = None;
311        let mut start_directory = None;
312        let mut target_window_index = None;
313        let mut insert_at_target = None;
314
315        while let Some(key) = map.next_key::<String>()? {
316            match key.as_str() {
317                "target" => target = Some(map.next_value()?),
318                "name" => name = Some(map.next_value()?),
319                "detached" => detached = Some(map.next_value()?),
320                "environment" => environment = Some(map.next_value()?),
321                "command" => command = Some(map.next_value()?),
322                "start_directory" => start_directory = Some(map.next_value()?),
323                "target_window_index" => target_window_index = Some(map.next_value()?),
324                "insert_at_target" => insert_at_target = Some(map.next_value()?),
325                _ => {
326                    let _: de::IgnoredAny = map.next_value()?;
327                }
328            }
329        }
330
331        Ok(NewWindowRequest {
332            target: target.ok_or_else(|| de::Error::missing_field("target"))?,
333            name: name.ok_or_else(|| de::Error::missing_field("name"))?,
334            detached: detached.ok_or_else(|| de::Error::missing_field("detached"))?,
335            environment: environment.ok_or_else(|| de::Error::missing_field("environment"))?,
336            command: command.unwrap_or_default(),
337            start_directory: start_directory.unwrap_or_default(),
338            target_window_index: target_window_index.unwrap_or_default(),
339            insert_at_target: insert_at_target.unwrap_or_default(),
340        })
341    }
342}
343
344struct RespawnWindowRequestVisitor;
345
346impl<'de> Visitor<'de> for RespawnWindowRequestVisitor {
347    type Value = RespawnWindowRequest;
348
349    fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
350        formatter.write_str("a respawn-window request")
351    }
352
353    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
354    where
355        A: SeqAccess<'de>,
356    {
357        let target = seq
358            .next_element()?
359            .ok_or_else(|| de::Error::invalid_length(0, &self))?;
360        let kill = seq
361            .next_element()?
362            .ok_or_else(|| de::Error::invalid_length(1, &self))?;
363        let environment = seq
364            .next_element()?
365            .ok_or_else(|| de::Error::invalid_length(2, &self))?;
366        let command = compat_next_element(&mut seq)?;
367        let start_directory = compat_next_element(&mut seq)?;
368
369        Ok(RespawnWindowRequest {
370            target,
371            kill,
372            environment,
373            command,
374            start_directory,
375        })
376    }
377
378    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
379    where
380        A: MapAccess<'de>,
381    {
382        let mut target = None;
383        let mut kill = None;
384        let mut environment = None;
385        let mut command = None;
386        let mut start_directory = None;
387
388        while let Some(key) = map.next_key::<String>()? {
389            match key.as_str() {
390                "target" => target = Some(map.next_value()?),
391                "kill" => kill = Some(map.next_value()?),
392                "environment" => environment = Some(map.next_value()?),
393                "command" => command = Some(map.next_value()?),
394                "start_directory" => start_directory = Some(map.next_value()?),
395                _ => {
396                    let _: de::IgnoredAny = map.next_value()?;
397                }
398            }
399        }
400
401        Ok(RespawnWindowRequest {
402            target: target.ok_or_else(|| de::Error::missing_field("target"))?,
403            kill: kill.ok_or_else(|| de::Error::missing_field("kill"))?,
404            environment: environment.ok_or_else(|| de::Error::missing_field("environment"))?,
405            command: command.unwrap_or_default(),
406            start_directory: start_directory.unwrap_or_default(),
407        })
408    }
409}