1use std::fmt;
8use std::str::FromStr;
9
10use serde::{Deserialize, Serialize};
11
12pub use crate::identity::SessionName;
13use crate::{PaneId, RmuxError};
14pub use rmux_types::{TerminalGeometry, TerminalPixels, TerminalSize};
15
16#[path = "types/hooks.rs"]
17mod hooks;
18#[path = "types/options.rs"]
19mod options;
20
21pub use hooks::{HookLifecycle, HookName};
22pub use options::{OptionName, SetOptionMode};
23
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
32#[non_exhaustive]
33pub enum ProcessCommand {
34 Argv(Vec<String>),
40 Shell(String),
42}
43
44impl ProcessCommand {
45 #[must_use]
48 pub fn from_legacy_command(command: Option<&[String]>) -> Option<Self> {
49 match command {
50 Some([single]) => Some(Self::Shell(single.clone())),
51 Some(argv) if !argv.is_empty() => Some(Self::Argv(argv.to_vec())),
52 _ => None,
53 }
54 }
55
56 #[must_use]
61 pub fn display_command(&self) -> Vec<String> {
62 match self {
63 Self::Argv(argv) => argv.clone(),
64 Self::Shell(command) => vec![command.clone()],
65 }
66 }
67
68 #[must_use]
70 pub fn is_empty(&self) -> bool {
71 match self {
72 Self::Argv(argv) => argv.is_empty() || argv.first().is_some_and(String::is_empty),
73 Self::Shell(command) => command.is_empty(),
74 }
75 }
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
81#[serde(transparent)]
82pub struct PaneOutputSubscriptionId(u64);
83
84impl PaneOutputSubscriptionId {
85 #[must_use]
87 pub const fn new(value: u64) -> Self {
88 Self(value)
89 }
90
91 #[must_use]
93 pub const fn as_u64(self) -> u64 {
94 self.0
95 }
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
101#[serde(transparent)]
102pub struct PaneStateSubscriptionId(u64);
103
104impl PaneStateSubscriptionId {
105 #[must_use]
107 pub const fn new(value: u64) -> Self {
108 Self(value)
109 }
110
111 #[must_use]
113 pub const fn as_u64(self) -> u64 {
114 self.0
115 }
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
125#[serde(transparent)]
126pub struct SdkWaitOwnerId(u64);
127
128impl SdkWaitOwnerId {
129 #[must_use]
131 pub const fn new(value: u64) -> Self {
132 Self(value)
133 }
134
135 #[must_use]
137 pub const fn as_u64(self) -> u64 {
138 self.0
139 }
140}
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
145#[serde(transparent)]
146pub struct SdkWaitId(u64);
147
148impl SdkWaitId {
149 #[must_use]
151 pub const fn new(value: u64) -> Self {
152 Self(value)
153 }
154
155 #[must_use]
157 pub const fn as_u64(self) -> u64 {
158 self.0
159 }
160}
161
162#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
164pub enum Target {
165 Session(SessionName),
167 Window(WindowTarget),
169 Pane(PaneTarget),
171}
172
173impl Target {
174 pub fn parse(value: &str) -> Result<Self, RmuxError> {
176 if let Some((session_name, tail)) = value.split_once(':') {
177 let session_name = SessionName::new(session_name.to_owned())?;
178
179 if !tail.is_empty() && tail.chars().all(|character| character.is_ascii_digit()) {
180 let window_index = parse_window_index(value, tail)?;
181 return Ok(Self::Window(WindowTarget::with_window(
182 session_name,
183 window_index,
184 )));
185 }
186
187 if let Some((window_index, pane_index)) = tail.split_once('.') {
188 let window_index = parse_window_index(value, window_index)?;
189 let pane_index = parse_pane_index(value, pane_index)?;
190 return Ok(Self::Pane(PaneTarget::with_window(
191 session_name,
192 window_index,
193 pane_index,
194 )));
195 }
196
197 return Err(RmuxError::invalid_target(
198 value,
199 "targets must match 'session', 'session:window', or 'session:window.pane'",
200 ));
201 }
202
203 Ok(Self::Session(SessionName::new(value.to_owned())?))
204 }
205
206 #[must_use]
208 pub fn session_name(&self) -> &SessionName {
209 match self {
210 Self::Session(session_name) => session_name,
211 Self::Window(target) => target.session_name(),
212 Self::Pane(target) => target.session_name(),
213 }
214 }
215}
216
217impl fmt::Display for Target {
218 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
219 match self {
220 Self::Session(session_name) => session_name.fmt(formatter),
221 Self::Window(target) => target.fmt(formatter),
222 Self::Pane(target) => target.fmt(formatter),
223 }
224 }
225}
226
227impl FromStr for Target {
228 type Err = RmuxError;
229
230 fn from_str(value: &str) -> Result<Self, Self::Err> {
231 Self::parse(value)
232 }
233}
234
235#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
237pub struct WindowTarget {
238 session_name: SessionName,
239 window_index: u32,
240}
241
242impl WindowTarget {
243 #[must_use]
245 pub const fn new(session_name: SessionName) -> Self {
246 Self::with_window(session_name, 0)
247 }
248
249 #[must_use]
251 pub const fn with_window(session_name: SessionName, window_index: u32) -> Self {
252 Self {
253 session_name,
254 window_index,
255 }
256 }
257
258 #[must_use]
260 pub const fn session_name(&self) -> &SessionName {
261 &self.session_name
262 }
263
264 #[must_use]
266 pub const fn window_index(&self) -> u32 {
267 self.window_index
268 }
269}
270
271impl fmt::Display for WindowTarget {
272 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
273 write!(formatter, "{}:{}", self.session_name, self.window_index)
274 }
275}
276
277#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
279pub struct PaneTarget {
280 session_name: SessionName,
281 window_index: u32,
282 pane_index: u32,
283}
284
285impl PaneTarget {
286 #[must_use]
288 pub const fn new(session_name: SessionName, pane_index: u32) -> Self {
289 Self::with_window(session_name, 0, pane_index)
290 }
291
292 #[must_use]
294 pub const fn with_window(
295 session_name: SessionName,
296 window_index: u32,
297 pane_index: u32,
298 ) -> Self {
299 Self {
300 session_name,
301 window_index,
302 pane_index,
303 }
304 }
305
306 #[must_use]
308 pub const fn session_name(&self) -> &SessionName {
309 &self.session_name
310 }
311
312 #[must_use]
314 pub const fn window_index(&self) -> u32 {
315 self.window_index
316 }
317
318 #[must_use]
320 pub const fn pane_index(&self) -> u32 {
321 self.pane_index
322 }
323}
324
325impl fmt::Display for PaneTarget {
326 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
327 write!(
328 formatter,
329 "{}:{}.{}",
330 self.session_name, self.window_index, self.pane_index
331 )
332 }
333}
334
335#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
338pub enum PaneTargetRef {
339 Slot(PaneTarget),
341 Id {
343 session_name: SessionName,
345 pane_id: PaneId,
347 },
348}
349
350impl PaneTargetRef {
351 #[must_use]
353 pub const fn slot(target: PaneTarget) -> Self {
354 Self::Slot(target)
355 }
356
357 #[must_use]
359 pub const fn by_id(session_name: SessionName, pane_id: PaneId) -> Self {
360 Self::Id {
361 session_name,
362 pane_id,
363 }
364 }
365
366 #[must_use]
368 pub const fn session_name(&self) -> &SessionName {
369 match self {
370 Self::Slot(target) => target.session_name(),
371 Self::Id { session_name, .. } => session_name,
372 }
373 }
374
375 #[must_use]
377 pub const fn pane_id(&self) -> Option<PaneId> {
378 match self {
379 Self::Slot(_) => None,
380 Self::Id { pane_id, .. } => Some(*pane_id),
381 }
382 }
383}
384
385impl From<PaneTarget> for PaneTargetRef {
386 fn from(value: PaneTarget) -> Self {
387 Self::Slot(value)
388 }
389}
390
391impl fmt::Display for PaneTargetRef {
392 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
393 match self {
394 Self::Slot(target) => target.fmt(formatter),
395 Self::Id {
396 session_name,
397 pane_id,
398 } => write!(formatter, "{session_name}:{pane_id}"),
399 }
400 }
401}
402
403#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
405pub enum ScopeSelector {
406 Global,
408 Session(SessionName),
410 Window(WindowTarget),
412 Pane(PaneTarget),
414}
415
416#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
418pub enum OptionScopeSelector {
419 ServerGlobal,
421 SessionGlobal,
423 WindowGlobal,
425 Session(SessionName),
427 Window(WindowTarget),
429 Pane(PaneTarget),
431}
432
433#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
435pub enum LayoutName {
436 MainVertical,
438 MainHorizontal,
440 EvenHorizontal,
442 EvenVertical,
444 Tiled,
446 MainHorizontalMirrored,
448 MainVerticalMirrored,
450}
451
452impl fmt::Display for LayoutName {
453 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
454 match self {
455 Self::MainVertical => formatter.write_str("main-vertical"),
456 Self::MainHorizontal => formatter.write_str("main-horizontal"),
457 Self::EvenHorizontal => formatter.write_str("even-horizontal"),
458 Self::EvenVertical => formatter.write_str("even-vertical"),
459 Self::Tiled => formatter.write_str("tiled"),
460 Self::MainHorizontalMirrored => formatter.write_str("main-horizontal-mirrored"),
461 Self::MainVerticalMirrored => formatter.write_str("main-vertical-mirrored"),
462 }
463 }
464}
465
466impl FromStr for LayoutName {
467 type Err = RmuxError;
468
469 fn from_str(value: &str) -> Result<Self, Self::Err> {
470 match value {
471 "main-vertical" => Ok(Self::MainVertical),
472 "main-horizontal" => Ok(Self::MainHorizontal),
473 "even-horizontal" => Ok(Self::EvenHorizontal),
474 "even-vertical" => Ok(Self::EvenVertical),
475 "tiled" => Ok(Self::Tiled),
476 "main-horizontal-mirrored" => Ok(Self::MainHorizontalMirrored),
477 "main-vertical-mirrored" => Ok(Self::MainVerticalMirrored),
478 _ => Err(RmuxError::Server(format!("unknown layout: {value}"))),
479 }
480 }
481}
482
483#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
492pub enum SplitDirection {
493 #[default]
496 Vertical,
497 Horizontal,
499}
500
501#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
503pub enum ResizePaneAdjustment {
504 AbsoluteWidth {
506 columns: u16,
508 },
509 AbsoluteHeight {
511 rows: u16,
513 },
514 Zoom,
516 Up {
518 cells: u16,
520 },
521 Down {
523 cells: u16,
525 },
526 Left {
528 cells: u16,
530 },
531 Right {
533 cells: u16,
535 },
536 NoOp,
538 AbsoluteSize {
540 columns: u16,
542 rows: u16,
544 },
545 TrimBelow,
547 Composite {
550 columns: Option<u16>,
552 rows: Option<u16>,
554 relative: Option<ResizePaneRelativeDirection>,
556 cells: u16,
558 },
559}
560
561#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
563pub enum ResizePaneRelativeDirection {
564 Up,
566 Down,
568 Left,
570 Right,
572}
573
574impl ResizePaneRelativeDirection {
575 #[must_use]
577 pub const fn to_adjustment(self, cells: u16) -> ResizePaneAdjustment {
578 match self {
579 Self::Up => ResizePaneAdjustment::Up { cells },
580 Self::Down => ResizePaneAdjustment::Down { cells },
581 Self::Left => ResizePaneAdjustment::Left { cells },
582 Self::Right => ResizePaneAdjustment::Right { cells },
583 }
584 }
585}
586
587fn parse_pane_index(target: &str, pane_index: &str) -> Result<u32, RmuxError> {
588 if pane_index.is_empty() {
589 return Err(RmuxError::invalid_target(
590 target,
591 "pane index must be an unsigned integer",
592 ));
593 }
594
595 pane_index
596 .parse::<u32>()
597 .map_err(|_| RmuxError::invalid_target(target, "pane index must be an unsigned integer"))
598}
599
600fn parse_window_index(target: &str, window_index: &str) -> Result<u32, RmuxError> {
601 if window_index.is_empty() {
602 return Err(RmuxError::invalid_target(
603 target,
604 "window index must be an unsigned integer",
605 ));
606 }
607
608 window_index
609 .parse::<u32>()
610 .map_err(|_| RmuxError::invalid_target(target, "window index must be an unsigned integer"))
611}
612
613#[cfg(test)]
614mod tests;