1use std::fmt;
9
10use serde::{Deserialize, Serialize};
11
12use crate::types::{PaneRef, TerminalSizeSpec};
13use crate::SessionName;
14
15#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17#[non_exhaustive]
18pub enum ProcessCommandSpec {
19 Argv(Vec<String>),
21 Shell(String),
23}
24
25impl From<ProcessCommandSpec> for rmux_proto::ProcessCommand {
26 fn from(value: ProcessCommandSpec) -> Self {
27 match value {
28 ProcessCommandSpec::Argv(argv) => Self::Argv(argv),
29 ProcessCommandSpec::Shell(command) => Self::Shell(command),
30 }
31 }
32}
33
34impl ProcessCommandSpec {
35 pub(crate) fn is_empty(&self) -> bool {
36 match self {
37 Self::Argv(argv) => argv.is_empty() || argv.first().is_some_and(String::is_empty),
38 Self::Shell(command) => command.is_empty(),
39 }
40 }
41}
42
43#[derive(Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
51pub struct ProcessSpec {
52 #[serde(default)]
55 pub command: Option<Vec<String>>,
56 #[serde(default)]
58 pub process_command: Option<ProcessCommandSpec>,
59 #[serde(default)]
61 pub environment: Option<Vec<String>>,
62}
63
64impl ProcessSpec {
65 #[must_use]
67 pub fn argv<I, S>(command: I) -> Self
68 where
69 I: IntoIterator<Item = S>,
70 S: Into<String>,
71 {
72 Self {
73 process_command: Some(ProcessCommandSpec::Argv(
74 command.into_iter().map(Into::into).collect(),
75 )),
76 ..Self::default()
77 }
78 }
79
80 #[must_use]
82 pub fn shell(command: impl Into<String>) -> Self {
83 Self {
84 process_command: Some(ProcessCommandSpec::Shell(command.into())),
85 ..Self::default()
86 }
87 }
88
89 pub(crate) fn into_proto_parts(
90 self,
91 ) -> (
92 Option<Vec<String>>,
93 Option<rmux_proto::ProcessCommand>,
94 Option<Vec<String>>,
95 ) {
96 (
97 self.command,
98 self.process_command.map(rmux_proto::ProcessCommand::from),
99 self.environment,
100 )
101 }
102}
103
104impl fmt::Debug for ProcessSpec {
105 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
106 formatter
107 .debug_struct("ProcessSpec")
108 .field("command", &self.command)
109 .field("process_command", &self.process_command)
110 .finish_non_exhaustive()
111 }
112}
113
114#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
116pub struct NewSessionReuse {
117 #[serde(default)]
119 pub attach_if_exists: bool,
120 #[serde(default)]
122 pub detach_other_clients: bool,
123 #[serde(default)]
125 pub kill_other_clients: bool,
126 #[serde(default)]
128 pub skip_environment_update: bool,
129 #[serde(default)]
131 pub flags: Option<Vec<String>>,
132}
133
134#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
136pub struct AttachSessionReuse {
137 #[serde(default)]
139 pub detach_other_clients: bool,
140 #[serde(default)]
142 pub kill_other_clients: bool,
143 #[serde(default)]
145 pub read_only: bool,
146 #[serde(default)]
148 pub skip_environment_update: bool,
149 #[serde(default)]
151 pub flags: Option<Vec<String>>,
152}
153
154#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
156pub struct ClientTerminalSpec {
157 #[serde(default)]
159 pub terminal_features: Vec<String>,
160 #[serde(default)]
162 pub utf8: bool,
163}
164
165impl From<ClientTerminalSpec> for rmux_proto::ClientTerminalContext {
166 fn from(value: ClientTerminalSpec) -> Self {
167 Self {
168 terminal_features: value.terminal_features,
169 utf8: value.utf8,
170 }
171 }
172}
173
174impl From<rmux_proto::ClientTerminalContext> for ClientTerminalSpec {
175 fn from(value: rmux_proto::ClientTerminalContext) -> Self {
176 Self {
177 terminal_features: value.terminal_features,
178 utf8: value.utf8,
179 }
180 }
181}
182
183#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
185pub struct NewSessionSpec {
186 #[serde(default)]
188 pub session_name: Option<SessionName>,
189 #[serde(default)]
191 pub working_directory: Option<String>,
192 #[serde(default)]
194 pub detached: bool,
195 #[serde(default)]
197 pub size: Option<TerminalSizeSpec>,
198 #[serde(default)]
200 pub process: ProcessSpec,
201 #[serde(default)]
203 pub group_target: Option<SessionName>,
204 #[serde(default)]
206 pub reuse: NewSessionReuse,
207 #[serde(default)]
209 pub window_name: Option<String>,
210 #[serde(default)]
212 pub print_session_info: bool,
213 #[serde(default)]
215 pub print_format: Option<String>,
216}
217
218impl From<NewSessionSpec> for rmux_proto::NewSessionExtRequest {
219 fn from(value: NewSessionSpec) -> Self {
220 let (command, process_command, environment) = value.process.into_proto_parts();
221 Self {
222 session_name: value.session_name,
223 working_directory: value.working_directory,
224 detached: value.detached,
225 size: value.size.map(Into::into),
226 environment,
227 group_target: value.group_target,
228 attach_if_exists: value.reuse.attach_if_exists,
229 detach_other_clients: value.reuse.detach_other_clients,
230 kill_other_clients: value.reuse.kill_other_clients,
231 flags: value.reuse.flags,
232 window_name: value.window_name,
233 print_session_info: value.print_session_info,
234 print_format: value.print_format,
235 command,
236 process_command,
237 client_environment: None,
238 skip_environment_update: value.reuse.skip_environment_update,
239 }
240 }
241}
242
243#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
245pub struct AttachSessionSpec {
246 #[serde(default)]
248 pub target: Option<SessionName>,
249 #[serde(default)]
251 pub target_spec: Option<String>,
252 #[serde(default)]
254 pub reuse: AttachSessionReuse,
255 #[serde(default)]
257 pub working_directory: Option<String>,
258 #[serde(default)]
260 pub client_terminal: ClientTerminalSpec,
261 #[serde(default)]
263 pub client_size: Option<TerminalSizeSpec>,
264}
265
266impl From<AttachSessionSpec> for rmux_proto::AttachSessionExt2Request {
267 fn from(value: AttachSessionSpec) -> Self {
268 Self {
269 target: value.target,
270 target_spec: value.target_spec,
271 detach_other_clients: value.reuse.detach_other_clients,
272 kill_other_clients: value.reuse.kill_other_clients,
273 read_only: value.reuse.read_only,
274 skip_environment_update: value.reuse.skip_environment_update,
275 flags: value.reuse.flags,
276 working_directory: value.working_directory,
277 client_terminal: value.client_terminal.into(),
278 client_size: value.client_size.map(Into::into),
279 }
280 }
281}
282
283#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
285pub struct SubscriptionSpec {
286 #[serde(default)]
288 pub subscriptions: Vec<String>,
289 #[serde(default)]
291 pub subscriptions_format: Vec<String>,
292}
293
294#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
296pub struct RefreshClientSpec {
297 #[serde(default)]
299 pub target_client: Option<String>,
300 #[serde(default)]
302 pub adjustment: Option<u32>,
303 #[serde(default)]
305 pub clear_pan: bool,
306 #[serde(default)]
308 pub pan_left: bool,
309 #[serde(default)]
311 pub pan_right: bool,
312 #[serde(default)]
314 pub pan_up: bool,
315 #[serde(default)]
317 pub pan_down: bool,
318 #[serde(default)]
320 pub status_only: bool,
321 #[serde(default)]
323 pub clipboard_query: bool,
324 #[serde(default)]
326 pub flags: Option<String>,
327 #[serde(default)]
329 pub flags_alias: Option<String>,
330 #[serde(default)]
332 pub subscriptions: SubscriptionSpec,
333 #[serde(default)]
335 pub control_size: Option<String>,
336 #[serde(default)]
338 pub colour_report: Option<String>,
339}
340
341impl From<RefreshClientSpec> for rmux_proto::RefreshClientRequest {
342 fn from(value: RefreshClientSpec) -> Self {
343 Self {
344 target_client: value.target_client,
345 adjustment: value.adjustment,
346 clear_pan: value.clear_pan,
347 pan_left: value.pan_left,
348 pan_right: value.pan_right,
349 pan_up: value.pan_up,
350 pan_down: value.pan_down,
351 status_only: value.status_only,
352 clipboard_query: value.clipboard_query,
353 flags: value.flags,
354 flags_alias: value.flags_alias,
355 subscriptions: value.subscriptions.subscriptions,
356 subscriptions_format: value.subscriptions.subscriptions_format,
357 control_size: value.control_size,
358 colour_report: value.colour_report,
359 }
360 }
361}
362
363#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
371pub enum SplitDirectionSpec {
372 #[default]
375 Vertical,
376 Horizontal,
378}
379
380impl From<SplitDirectionSpec> for rmux_proto::SplitDirection {
381 fn from(value: SplitDirectionSpec) -> Self {
382 match value {
383 SplitDirectionSpec::Vertical => Self::Vertical,
384 SplitDirectionSpec::Horizontal => Self::Horizontal,
385 }
386 }
387}
388
389impl From<rmux_proto::SplitDirection> for SplitDirectionSpec {
390 fn from(value: rmux_proto::SplitDirection) -> Self {
391 match value {
392 rmux_proto::SplitDirection::Vertical => Self::Vertical,
393 rmux_proto::SplitDirection::Horizontal => Self::Horizontal,
394 }
395 }
396}
397
398#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
400pub enum SplitTargetSpec {
401 Session(SessionName),
403 Pane(PaneRef),
405}
406
407impl From<SplitTargetSpec> for rmux_proto::SplitWindowTarget {
408 fn from(value: SplitTargetSpec) -> Self {
409 match value {
410 SplitTargetSpec::Session(session_name) => Self::Session(session_name),
411 SplitTargetSpec::Pane(target) => Self::Pane(target.into()),
412 }
413 }
414}
415
416impl From<rmux_proto::SplitWindowTarget> for SplitTargetSpec {
417 fn from(value: rmux_proto::SplitWindowTarget) -> Self {
418 match value {
419 rmux_proto::SplitWindowTarget::Session(session_name) => Self::Session(session_name),
420 rmux_proto::SplitWindowTarget::Pane(target) => Self::Pane(target.into()),
421 }
422 }
423}
424
425impl From<&SplitTargetSpec> for rmux_proto::SplitWindowTarget {
426 fn from(value: &SplitTargetSpec) -> Self {
427 match value {
428 SplitTargetSpec::Session(session_name) => Self::Session(session_name.clone()),
429 SplitTargetSpec::Pane(target) => Self::Pane(target.into()),
430 }
431 }
432}
433
434#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
436pub struct SplitSpec {
437 pub target: SplitTargetSpec,
439 #[serde(default)]
441 pub direction: SplitDirectionSpec,
442 #[serde(default)]
445 pub before: bool,
446 #[serde(default)]
448 pub process: ProcessSpec,
449}
450
451impl From<SplitSpec> for rmux_proto::SplitWindowExtRequest {
452 fn from(value: SplitSpec) -> Self {
453 let (command, process_command, environment) = value.process.into_proto_parts();
454 Self {
455 target: value.target.into(),
456 direction: value.direction.into(),
457 before: value.before,
458 environment,
459 command,
460 process_command,
461 start_directory: None,
462 keep_alive_on_exit: None,
463 detached: false,
464 size: None,
465 preserve_zoom: false,
466 full_size: false,
467 stdin_payload: None,
468 }
469 }
470}