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, Deserialize)]
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}
143
144/// Request payload for `swap-window`.
145#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
146pub struct SwapWindowRequest {
147    /// The source window slot.
148    pub source: WindowTarget,
149    /// The destination window slot.
150    pub target: WindowTarget,
151    /// Whether the swapped destination slots should become active after the swap.
152    pub detached: bool,
153}
154
155/// The supported pane rotation directions for `rotate-window`.
156#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
157pub enum RotateWindowDirection {
158    /// Move the last pane to the head.
159    Down,
160    /// Move the first pane to the tail.
161    Up,
162}
163
164/// Request payload for `rotate-window`.
165#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
166pub struct RotateWindowRequest {
167    /// The addressed window.
168    pub target: WindowTarget,
169    /// The requested rotation direction.
170    pub direction: RotateWindowDirection,
171    /// Whether to save and restore zoom state around the rotation (`-Z`).
172    #[serde(default)]
173    pub restore_zoom: bool,
174}
175
176/// Request payload for `resize-window`.
177#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
178pub struct ResizeWindowRequest {
179    /// The addressed window.
180    pub target: WindowTarget,
181    /// Optional explicit width (`-x`).
182    pub width: Option<u16>,
183    /// Optional explicit height (`-y`).
184    pub height: Option<u16>,
185    /// Relative adjustment (from `-D`, `-U`, `-L`, `-R`).
186    #[serde(default)]
187    pub adjustment: Option<ResizeWindowAdjustment>,
188}
189
190/// Directional relative-size adjustment for `resize-window`.
191#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
192pub enum ResizeWindowAdjustment {
193    /// Shrink height (`-U`).
194    Up(u16),
195    /// Grow height (`-D`).
196    Down(u16),
197    /// Shrink width (`-L`).
198    Left(u16),
199    /// Grow width (`-R`).
200    Right(u16),
201}
202
203/// Request payload for `respawn-window`.
204#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
205pub struct RespawnWindowRequest {
206    /// The addressed window.
207    pub target: WindowTarget,
208    /// Whether to kill existing panes even when they are still running (`-k`).
209    #[serde(default)]
210    pub kill: bool,
211    /// Optional per-spawn environment overrides in `NAME=VALUE` form.
212    #[serde(default)]
213    pub environment: Option<Vec<String>>,
214    /// Optional shell command argv. A single argument is executed via `$SHELL -c`.
215    #[serde(default)]
216    pub command: Option<Vec<String>>,
217    /// Optional working-directory override.
218    #[serde(default)]
219    pub start_directory: Option<PathBuf>,
220}
221
222impl<'de> Deserialize<'de> for NewWindowRequest {
223    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
224    where
225        D: Deserializer<'de>,
226    {
227        deserializer.deserialize_struct(
228            "NewWindowRequest",
229            &[
230                "target",
231                "name",
232                "detached",
233                "environment",
234                "command",
235                "start_directory",
236                "target_window_index",
237                "insert_at_target",
238                "process_command",
239            ],
240            NewWindowRequestVisitor,
241        )
242    }
243}
244
245impl<'de> Deserialize<'de> for RespawnWindowRequest {
246    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
247    where
248        D: Deserializer<'de>,
249    {
250        deserializer.deserialize_struct(
251            "RespawnWindowRequest",
252            &[
253                "target",
254                "kill",
255                "environment",
256                "command",
257                "start_directory",
258            ],
259            RespawnWindowRequestVisitor,
260        )
261    }
262}
263
264struct NewWindowRequestVisitor;
265
266impl<'de> Visitor<'de> for NewWindowRequestVisitor {
267    type Value = NewWindowRequest;
268
269    fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270        formatter.write_str("a new-window request")
271    }
272
273    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
274    where
275        A: SeqAccess<'de>,
276    {
277        let target = seq
278            .next_element()?
279            .ok_or_else(|| de::Error::invalid_length(0, &self))?;
280        let name = seq
281            .next_element()?
282            .ok_or_else(|| de::Error::invalid_length(1, &self))?;
283        let detached = seq
284            .next_element()?
285            .ok_or_else(|| de::Error::invalid_length(2, &self))?;
286        let environment = seq
287            .next_element()?
288            .ok_or_else(|| de::Error::invalid_length(3, &self))?;
289        let command = compat_next_element(&mut seq)?;
290        let start_directory = compat_next_element(&mut seq)?;
291        let target_window_index = compat_next_element(&mut seq)?;
292        let insert_at_target = compat_next_element(&mut seq)?;
293        let process_command = compat_next_element(&mut seq)?;
294
295        Ok(NewWindowRequest {
296            target,
297            name,
298            detached,
299            environment,
300            command,
301            start_directory,
302            target_window_index,
303            insert_at_target,
304            process_command,
305        })
306    }
307
308    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
309    where
310        A: MapAccess<'de>,
311    {
312        let mut target = None;
313        let mut name = None;
314        let mut detached = None;
315        let mut environment = None;
316        let mut command = None;
317        let mut process_command = None;
318        let mut start_directory = None;
319        let mut target_window_index = None;
320        let mut insert_at_target = None;
321
322        while let Some(key) = map.next_key::<String>()? {
323            match key.as_str() {
324                "target" => target = Some(map.next_value()?),
325                "name" => name = Some(map.next_value()?),
326                "detached" => detached = Some(map.next_value()?),
327                "environment" => environment = Some(map.next_value()?),
328                "command" => command = Some(map.next_value()?),
329                "process_command" => process_command = Some(map.next_value()?),
330                "start_directory" => start_directory = Some(map.next_value()?),
331                "target_window_index" => target_window_index = Some(map.next_value()?),
332                "insert_at_target" => insert_at_target = Some(map.next_value()?),
333                _ => {
334                    let _: de::IgnoredAny = map.next_value()?;
335                }
336            }
337        }
338
339        Ok(NewWindowRequest {
340            target: target.ok_or_else(|| de::Error::missing_field("target"))?,
341            name: name.ok_or_else(|| de::Error::missing_field("name"))?,
342            detached: detached.ok_or_else(|| de::Error::missing_field("detached"))?,
343            environment: environment.ok_or_else(|| de::Error::missing_field("environment"))?,
344            command: command.unwrap_or_default(),
345            process_command: process_command.unwrap_or_default(),
346            start_directory: start_directory.unwrap_or_default(),
347            target_window_index: target_window_index.unwrap_or_default(),
348            insert_at_target: insert_at_target.unwrap_or_default(),
349        })
350    }
351}
352
353struct RespawnWindowRequestVisitor;
354
355impl<'de> Visitor<'de> for RespawnWindowRequestVisitor {
356    type Value = RespawnWindowRequest;
357
358    fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
359        formatter.write_str("a respawn-window request")
360    }
361
362    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
363    where
364        A: SeqAccess<'de>,
365    {
366        let target = seq
367            .next_element()?
368            .ok_or_else(|| de::Error::invalid_length(0, &self))?;
369        let kill = seq
370            .next_element()?
371            .ok_or_else(|| de::Error::invalid_length(1, &self))?;
372        let environment = seq
373            .next_element()?
374            .ok_or_else(|| de::Error::invalid_length(2, &self))?;
375        let command = compat_next_element(&mut seq)?;
376        let start_directory = compat_next_element(&mut seq)?;
377
378        Ok(RespawnWindowRequest {
379            target,
380            kill,
381            environment,
382            command,
383            start_directory,
384        })
385    }
386
387    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
388    where
389        A: MapAccess<'de>,
390    {
391        let mut target = None;
392        let mut kill = None;
393        let mut environment = None;
394        let mut command = None;
395        let mut start_directory = None;
396
397        while let Some(key) = map.next_key::<String>()? {
398            match key.as_str() {
399                "target" => target = Some(map.next_value()?),
400                "kill" => kill = Some(map.next_value()?),
401                "environment" => environment = Some(map.next_value()?),
402                "command" => command = Some(map.next_value()?),
403                "start_directory" => start_directory = Some(map.next_value()?),
404                _ => {
405                    let _: de::IgnoredAny = map.next_value()?;
406                }
407            }
408        }
409
410        Ok(RespawnWindowRequest {
411            target: target.ok_or_else(|| de::Error::missing_field("target"))?,
412            kill: kill.ok_or_else(|| de::Error::missing_field("kill"))?,
413            environment: environment.ok_or_else(|| de::Error::missing_field("environment"))?,
414            command: command.unwrap_or_default(),
415            start_directory: start_directory.unwrap_or_default(),
416        })
417    }
418}