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::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>),
36 Shell(String),
38}
39
40impl ProcessCommand {
41 #[must_use]
44 pub fn from_legacy_command(command: Option<&[String]>) -> Option<Self> {
45 match command {
46 Some([single]) => Some(Self::Shell(single.clone())),
47 Some(argv) if !argv.is_empty() => Some(Self::Argv(argv.to_vec())),
48 _ => None,
49 }
50 }
51
52 #[must_use]
57 pub fn display_command(&self) -> Vec<String> {
58 match self {
59 Self::Argv(argv) => argv.clone(),
60 Self::Shell(command) => vec![command.clone()],
61 }
62 }
63
64 #[must_use]
66 pub fn is_empty(&self) -> bool {
67 match self {
68 Self::Argv(argv) => argv.is_empty() || argv.first().is_some_and(String::is_empty),
69 Self::Shell(command) => command.is_empty(),
70 }
71 }
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
77#[serde(transparent)]
78pub struct PaneOutputSubscriptionId(u64);
79
80impl PaneOutputSubscriptionId {
81 #[must_use]
83 pub const fn new(value: u64) -> Self {
84 Self(value)
85 }
86
87 #[must_use]
89 pub const fn as_u64(self) -> u64 {
90 self.0
91 }
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
101#[serde(transparent)]
102pub struct SdkWaitOwnerId(u64);
103
104impl SdkWaitOwnerId {
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)]
121#[serde(transparent)]
122pub struct SdkWaitId(u64);
123
124impl SdkWaitId {
125 #[must_use]
127 pub const fn new(value: u64) -> Self {
128 Self(value)
129 }
130
131 #[must_use]
133 pub const fn as_u64(self) -> u64 {
134 self.0
135 }
136}
137
138#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
140pub enum Target {
141 Session(SessionName),
143 Window(WindowTarget),
145 Pane(PaneTarget),
147}
148
149impl Target {
150 pub fn parse(value: &str) -> Result<Self, RmuxError> {
152 if let Some((session_name, tail)) = value.split_once(':') {
153 let session_name = SessionName::new(session_name.to_owned())?;
154
155 if !tail.is_empty() && tail.chars().all(|character| character.is_ascii_digit()) {
156 let window_index = parse_window_index(value, tail)?;
157 return Ok(Self::Window(WindowTarget::with_window(
158 session_name,
159 window_index,
160 )));
161 }
162
163 if let Some((window_index, pane_index)) = tail.split_once('.') {
164 let window_index = parse_window_index(value, window_index)?;
165 let pane_index = parse_pane_index(value, pane_index)?;
166 return Ok(Self::Pane(PaneTarget::with_window(
167 session_name,
168 window_index,
169 pane_index,
170 )));
171 }
172
173 return Err(RmuxError::invalid_target(
174 value,
175 "targets must match 'session', 'session:window', or 'session:window.pane'",
176 ));
177 }
178
179 Ok(Self::Session(SessionName::new(value.to_owned())?))
180 }
181
182 #[must_use]
184 pub fn session_name(&self) -> &SessionName {
185 match self {
186 Self::Session(session_name) => session_name,
187 Self::Window(target) => target.session_name(),
188 Self::Pane(target) => target.session_name(),
189 }
190 }
191}
192
193impl fmt::Display for Target {
194 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
195 match self {
196 Self::Session(session_name) => session_name.fmt(formatter),
197 Self::Window(target) => target.fmt(formatter),
198 Self::Pane(target) => target.fmt(formatter),
199 }
200 }
201}
202
203impl FromStr for Target {
204 type Err = RmuxError;
205
206 fn from_str(value: &str) -> Result<Self, Self::Err> {
207 Self::parse(value)
208 }
209}
210
211#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
213pub struct WindowTarget {
214 session_name: SessionName,
215 window_index: u32,
216}
217
218impl WindowTarget {
219 #[must_use]
221 pub const fn new(session_name: SessionName) -> Self {
222 Self::with_window(session_name, 0)
223 }
224
225 #[must_use]
227 pub const fn with_window(session_name: SessionName, window_index: u32) -> Self {
228 Self {
229 session_name,
230 window_index,
231 }
232 }
233
234 #[must_use]
236 pub const fn session_name(&self) -> &SessionName {
237 &self.session_name
238 }
239
240 #[must_use]
242 pub const fn window_index(&self) -> u32 {
243 self.window_index
244 }
245}
246
247impl fmt::Display for WindowTarget {
248 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
249 write!(formatter, "{}:{}", self.session_name, self.window_index)
250 }
251}
252
253#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
255pub struct PaneTarget {
256 session_name: SessionName,
257 window_index: u32,
258 pane_index: u32,
259}
260
261impl PaneTarget {
262 #[must_use]
264 pub const fn new(session_name: SessionName, pane_index: u32) -> Self {
265 Self::with_window(session_name, 0, pane_index)
266 }
267
268 #[must_use]
270 pub const fn with_window(
271 session_name: SessionName,
272 window_index: u32,
273 pane_index: u32,
274 ) -> Self {
275 Self {
276 session_name,
277 window_index,
278 pane_index,
279 }
280 }
281
282 #[must_use]
284 pub const fn session_name(&self) -> &SessionName {
285 &self.session_name
286 }
287
288 #[must_use]
290 pub const fn window_index(&self) -> u32 {
291 self.window_index
292 }
293
294 #[must_use]
296 pub const fn pane_index(&self) -> u32 {
297 self.pane_index
298 }
299}
300
301impl fmt::Display for PaneTarget {
302 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
303 write!(
304 formatter,
305 "{}:{}.{}",
306 self.session_name, self.window_index, self.pane_index
307 )
308 }
309}
310
311#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
314pub enum PaneTargetRef {
315 Slot(PaneTarget),
317 Id {
319 session_name: SessionName,
321 pane_id: PaneId,
323 },
324}
325
326impl PaneTargetRef {
327 #[must_use]
329 pub const fn slot(target: PaneTarget) -> Self {
330 Self::Slot(target)
331 }
332
333 #[must_use]
335 pub const fn by_id(session_name: SessionName, pane_id: PaneId) -> Self {
336 Self::Id {
337 session_name,
338 pane_id,
339 }
340 }
341
342 #[must_use]
344 pub const fn session_name(&self) -> &SessionName {
345 match self {
346 Self::Slot(target) => target.session_name(),
347 Self::Id { session_name, .. } => session_name,
348 }
349 }
350}
351
352impl From<PaneTarget> for PaneTargetRef {
353 fn from(value: PaneTarget) -> Self {
354 Self::Slot(value)
355 }
356}
357
358impl fmt::Display for PaneTargetRef {
359 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
360 match self {
361 Self::Slot(target) => target.fmt(formatter),
362 Self::Id {
363 session_name,
364 pane_id,
365 } => write!(formatter, "{session_name}:{pane_id}"),
366 }
367 }
368}
369
370#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
372pub enum ScopeSelector {
373 Global,
375 Session(SessionName),
377 Window(WindowTarget),
379 Pane(PaneTarget),
381}
382
383#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
385pub enum OptionScopeSelector {
386 ServerGlobal,
388 SessionGlobal,
390 WindowGlobal,
392 Session(SessionName),
394 Window(WindowTarget),
396 Pane(PaneTarget),
398}
399
400#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
402pub enum LayoutName {
403 MainVertical,
405 MainHorizontal,
407 EvenHorizontal,
409 EvenVertical,
411 Tiled,
413 MainHorizontalMirrored,
415 MainVerticalMirrored,
417}
418
419impl fmt::Display for LayoutName {
420 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
421 match self {
422 Self::MainVertical => formatter.write_str("main-vertical"),
423 Self::MainHorizontal => formatter.write_str("main-horizontal"),
424 Self::EvenHorizontal => formatter.write_str("even-horizontal"),
425 Self::EvenVertical => formatter.write_str("even-vertical"),
426 Self::Tiled => formatter.write_str("tiled"),
427 Self::MainHorizontalMirrored => formatter.write_str("main-horizontal-mirrored"),
428 Self::MainVerticalMirrored => formatter.write_str("main-vertical-mirrored"),
429 }
430 }
431}
432
433impl FromStr for LayoutName {
434 type Err = RmuxError;
435
436 fn from_str(value: &str) -> Result<Self, Self::Err> {
437 match value {
438 "main-vertical" => Ok(Self::MainVertical),
439 "main-horizontal" => Ok(Self::MainHorizontal),
440 "even-horizontal" => Ok(Self::EvenHorizontal),
441 "even-vertical" => Ok(Self::EvenVertical),
442 "tiled" => Ok(Self::Tiled),
443 "main-horizontal-mirrored" => Ok(Self::MainHorizontalMirrored),
444 "main-vertical-mirrored" => Ok(Self::MainVerticalMirrored),
445 _ => Err(RmuxError::Server(format!("unknown layout: {value}"))),
446 }
447 }
448}
449
450#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
459pub enum SplitDirection {
460 #[default]
463 Vertical,
464 Horizontal,
466}
467
468#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
470pub enum ResizePaneAdjustment {
471 AbsoluteWidth {
473 columns: u16,
475 },
476 AbsoluteHeight {
478 rows: u16,
480 },
481 Zoom,
483 Up {
485 cells: u16,
487 },
488 Down {
490 cells: u16,
492 },
493 Left {
495 cells: u16,
497 },
498 Right {
500 cells: u16,
502 },
503 NoOp,
505}
506
507fn parse_pane_index(target: &str, pane_index: &str) -> Result<u32, RmuxError> {
508 if pane_index.is_empty() {
509 return Err(RmuxError::invalid_target(
510 target,
511 "pane index must be an unsigned integer",
512 ));
513 }
514
515 pane_index
516 .parse::<u32>()
517 .map_err(|_| RmuxError::invalid_target(target, "pane index must be an unsigned integer"))
518}
519
520fn parse_window_index(target: &str, window_index: &str) -> Result<u32, RmuxError> {
521 if window_index.is_empty() {
522 return Err(RmuxError::invalid_target(
523 target,
524 "window index must be an unsigned integer",
525 ));
526 }
527
528 window_index
529 .parse::<u32>()
530 .map_err(|_| RmuxError::invalid_target(target, "window index must be an unsigned integer"))
531}
532
533#[cfg(test)]
534mod tests;