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>),
39 Shell(String),
41}
42
43impl ProcessCommand {
44 #[must_use]
47 pub fn from_legacy_command(command: Option<&[String]>) -> Option<Self> {
48 match command {
49 Some([single]) => Some(Self::Shell(single.clone())),
50 Some(argv) if !argv.is_empty() => Some(Self::Argv(argv.to_vec())),
51 _ => None,
52 }
53 }
54
55 #[must_use]
60 pub fn display_command(&self) -> Vec<String> {
61 match self {
62 Self::Argv(argv) => argv.clone(),
63 Self::Shell(command) => vec![command.clone()],
64 }
65 }
66
67 #[must_use]
69 pub fn is_empty(&self) -> bool {
70 match self {
71 Self::Argv(argv) => argv.is_empty() || argv.first().is_some_and(String::is_empty),
72 Self::Shell(command) => command.is_empty(),
73 }
74 }
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
80#[serde(transparent)]
81pub struct PaneOutputSubscriptionId(u64);
82
83impl PaneOutputSubscriptionId {
84 #[must_use]
86 pub const fn new(value: u64) -> Self {
87 Self(value)
88 }
89
90 #[must_use]
92 pub const fn as_u64(self) -> u64 {
93 self.0
94 }
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
100#[serde(transparent)]
101pub struct PaneStateSubscriptionId(u64);
102
103impl PaneStateSubscriptionId {
104 #[must_use]
106 pub const fn new(value: u64) -> Self {
107 Self(value)
108 }
109
110 #[must_use]
112 pub const fn as_u64(self) -> u64 {
113 self.0
114 }
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
124#[serde(transparent)]
125pub struct SdkWaitOwnerId(u64);
126
127impl SdkWaitOwnerId {
128 #[must_use]
130 pub const fn new(value: u64) -> Self {
131 Self(value)
132 }
133
134 #[must_use]
136 pub const fn as_u64(self) -> u64 {
137 self.0
138 }
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
144#[serde(transparent)]
145pub struct SdkWaitId(u64);
146
147impl SdkWaitId {
148 #[must_use]
150 pub const fn new(value: u64) -> Self {
151 Self(value)
152 }
153
154 #[must_use]
156 pub const fn as_u64(self) -> u64 {
157 self.0
158 }
159}
160
161#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
163pub enum Target {
164 Session(SessionName),
166 Window(WindowTarget),
168 Pane(PaneTarget),
170}
171
172impl Target {
173 pub fn parse(value: &str) -> Result<Self, RmuxError> {
175 if let Some((session_name, tail)) = value.split_once(':') {
176 let session_name = SessionName::new(session_name.to_owned())?;
177
178 if !tail.is_empty() && tail.chars().all(|character| character.is_ascii_digit()) {
179 let window_index = parse_window_index(value, tail)?;
180 return Ok(Self::Window(WindowTarget::with_window(
181 session_name,
182 window_index,
183 )));
184 }
185
186 if let Some((window_index, pane_index)) = tail.split_once('.') {
187 let window_index = parse_window_index(value, window_index)?;
188 let pane_index = parse_pane_index(value, pane_index)?;
189 return Ok(Self::Pane(PaneTarget::with_window(
190 session_name,
191 window_index,
192 pane_index,
193 )));
194 }
195
196 return Err(RmuxError::invalid_target(
197 value,
198 "targets must match 'session', 'session:window', or 'session:window.pane'",
199 ));
200 }
201
202 Ok(Self::Session(SessionName::new(value.to_owned())?))
203 }
204
205 #[must_use]
207 pub fn session_name(&self) -> &SessionName {
208 match self {
209 Self::Session(session_name) => session_name,
210 Self::Window(target) => target.session_name(),
211 Self::Pane(target) => target.session_name(),
212 }
213 }
214}
215
216impl fmt::Display for Target {
217 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
218 match self {
219 Self::Session(session_name) => session_name.fmt(formatter),
220 Self::Window(target) => target.fmt(formatter),
221 Self::Pane(target) => target.fmt(formatter),
222 }
223 }
224}
225
226impl FromStr for Target {
227 type Err = RmuxError;
228
229 fn from_str(value: &str) -> Result<Self, Self::Err> {
230 Self::parse(value)
231 }
232}
233
234#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
236pub struct WindowTarget {
237 session_name: SessionName,
238 window_index: u32,
239}
240
241impl WindowTarget {
242 #[must_use]
244 pub const fn new(session_name: SessionName) -> Self {
245 Self::with_window(session_name, 0)
246 }
247
248 #[must_use]
250 pub const fn with_window(session_name: SessionName, window_index: u32) -> Self {
251 Self {
252 session_name,
253 window_index,
254 }
255 }
256
257 #[must_use]
259 pub const fn session_name(&self) -> &SessionName {
260 &self.session_name
261 }
262
263 #[must_use]
265 pub const fn window_index(&self) -> u32 {
266 self.window_index
267 }
268}
269
270impl fmt::Display for WindowTarget {
271 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
272 write!(formatter, "{}:{}", self.session_name, self.window_index)
273 }
274}
275
276#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
278pub struct PaneTarget {
279 session_name: SessionName,
280 window_index: u32,
281 pane_index: u32,
282}
283
284impl PaneTarget {
285 #[must_use]
287 pub const fn new(session_name: SessionName, pane_index: u32) -> Self {
288 Self::with_window(session_name, 0, pane_index)
289 }
290
291 #[must_use]
293 pub const fn with_window(
294 session_name: SessionName,
295 window_index: u32,
296 pane_index: u32,
297 ) -> Self {
298 Self {
299 session_name,
300 window_index,
301 pane_index,
302 }
303 }
304
305 #[must_use]
307 pub const fn session_name(&self) -> &SessionName {
308 &self.session_name
309 }
310
311 #[must_use]
313 pub const fn window_index(&self) -> u32 {
314 self.window_index
315 }
316
317 #[must_use]
319 pub const fn pane_index(&self) -> u32 {
320 self.pane_index
321 }
322}
323
324impl fmt::Display for PaneTarget {
325 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
326 write!(
327 formatter,
328 "{}:{}.{}",
329 self.session_name, self.window_index, self.pane_index
330 )
331 }
332}
333
334#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
337pub enum PaneTargetRef {
338 Slot(PaneTarget),
340 Id {
342 session_name: SessionName,
344 pane_id: PaneId,
346 },
347}
348
349impl PaneTargetRef {
350 #[must_use]
352 pub const fn slot(target: PaneTarget) -> Self {
353 Self::Slot(target)
354 }
355
356 #[must_use]
358 pub const fn by_id(session_name: SessionName, pane_id: PaneId) -> Self {
359 Self::Id {
360 session_name,
361 pane_id,
362 }
363 }
364
365 #[must_use]
367 pub const fn session_name(&self) -> &SessionName {
368 match self {
369 Self::Slot(target) => target.session_name(),
370 Self::Id { session_name, .. } => session_name,
371 }
372 }
373
374 #[must_use]
376 pub const fn pane_id(&self) -> Option<PaneId> {
377 match self {
378 Self::Slot(_) => None,
379 Self::Id { pane_id, .. } => Some(*pane_id),
380 }
381 }
382}
383
384impl From<PaneTarget> for PaneTargetRef {
385 fn from(value: PaneTarget) -> Self {
386 Self::Slot(value)
387 }
388}
389
390impl fmt::Display for PaneTargetRef {
391 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
392 match self {
393 Self::Slot(target) => target.fmt(formatter),
394 Self::Id {
395 session_name,
396 pane_id,
397 } => write!(formatter, "{session_name}:{pane_id}"),
398 }
399 }
400}
401
402#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
404pub enum ScopeSelector {
405 Global,
407 Session(SessionName),
409 Window(WindowTarget),
411 Pane(PaneTarget),
413}
414
415#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
417pub enum OptionScopeSelector {
418 ServerGlobal,
420 SessionGlobal,
422 WindowGlobal,
424 Session(SessionName),
426 Window(WindowTarget),
428 Pane(PaneTarget),
430}
431
432#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
434pub enum LayoutName {
435 MainVertical,
437 MainHorizontal,
439 EvenHorizontal,
441 EvenVertical,
443 Tiled,
445 MainHorizontalMirrored,
447 MainVerticalMirrored,
449}
450
451impl fmt::Display for LayoutName {
452 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
453 match self {
454 Self::MainVertical => formatter.write_str("main-vertical"),
455 Self::MainHorizontal => formatter.write_str("main-horizontal"),
456 Self::EvenHorizontal => formatter.write_str("even-horizontal"),
457 Self::EvenVertical => formatter.write_str("even-vertical"),
458 Self::Tiled => formatter.write_str("tiled"),
459 Self::MainHorizontalMirrored => formatter.write_str("main-horizontal-mirrored"),
460 Self::MainVerticalMirrored => formatter.write_str("main-vertical-mirrored"),
461 }
462 }
463}
464
465impl FromStr for LayoutName {
466 type Err = RmuxError;
467
468 fn from_str(value: &str) -> Result<Self, Self::Err> {
469 match value {
470 "main-vertical" => Ok(Self::MainVertical),
471 "main-horizontal" => Ok(Self::MainHorizontal),
472 "even-horizontal" => Ok(Self::EvenHorizontal),
473 "even-vertical" => Ok(Self::EvenVertical),
474 "tiled" => Ok(Self::Tiled),
475 "main-horizontal-mirrored" => Ok(Self::MainHorizontalMirrored),
476 "main-vertical-mirrored" => Ok(Self::MainVerticalMirrored),
477 _ => Err(RmuxError::Server(format!("unknown layout: {value}"))),
478 }
479 }
480}
481
482#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
491pub enum SplitDirection {
492 #[default]
495 Vertical,
496 Horizontal,
498}
499
500#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
502pub enum ResizePaneAdjustment {
503 AbsoluteWidth {
505 columns: u16,
507 },
508 AbsoluteHeight {
510 rows: u16,
512 },
513 Zoom,
515 Up {
517 cells: u16,
519 },
520 Down {
522 cells: u16,
524 },
525 Left {
527 cells: u16,
529 },
530 Right {
532 cells: u16,
534 },
535 NoOp,
537 AbsoluteSize {
539 columns: u16,
541 rows: u16,
543 },
544 TrimBelow,
546 Composite {
549 columns: Option<u16>,
551 rows: Option<u16>,
553 relative: Option<ResizePaneRelativeDirection>,
555 cells: u16,
557 },
558}
559
560#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
562pub enum ResizePaneRelativeDirection {
563 Up,
565 Down,
567 Left,
569 Right,
571}
572
573impl ResizePaneRelativeDirection {
574 #[must_use]
576 pub const fn to_adjustment(self, cells: u16) -> ResizePaneAdjustment {
577 match self {
578 Self::Up => ResizePaneAdjustment::Up { cells },
579 Self::Down => ResizePaneAdjustment::Down { cells },
580 Self::Left => ResizePaneAdjustment::Left { cells },
581 Self::Right => ResizePaneAdjustment::Right { cells },
582 }
583 }
584}
585
586fn parse_pane_index(target: &str, pane_index: &str) -> Result<u32, RmuxError> {
587 if pane_index.is_empty() {
588 return Err(RmuxError::invalid_target(
589 target,
590 "pane index must be an unsigned integer",
591 ));
592 }
593
594 pane_index
595 .parse::<u32>()
596 .map_err(|_| RmuxError::invalid_target(target, "pane index must be an unsigned integer"))
597}
598
599fn parse_window_index(target: &str, window_index: &str) -> Result<u32, RmuxError> {
600 if window_index.is_empty() {
601 return Err(RmuxError::invalid_target(
602 target,
603 "window index must be an unsigned integer",
604 ));
605 }
606
607 window_index
608 .parse::<u32>()
609 .map_err(|_| RmuxError::invalid_target(target, "window index must be an unsigned integer"))
610}
611
612#[cfg(test)]
613mod tests;