1use std::fmt;
8use std::str::FromStr;
9
10use serde::{Deserialize, Serialize};
11
12pub use crate::identity::SessionName;
13use crate::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, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
27#[serde(transparent)]
28pub struct PaneOutputSubscriptionId(u64);
29
30impl PaneOutputSubscriptionId {
31 #[must_use]
33 pub const fn new(value: u64) -> Self {
34 Self(value)
35 }
36
37 #[must_use]
39 pub const fn as_u64(self) -> u64 {
40 self.0
41 }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
51#[serde(transparent)]
52pub struct SdkWaitOwnerId(u64);
53
54impl SdkWaitOwnerId {
55 #[must_use]
57 pub const fn new(value: u64) -> Self {
58 Self(value)
59 }
60
61 #[must_use]
63 pub const fn as_u64(self) -> u64 {
64 self.0
65 }
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
71#[serde(transparent)]
72pub struct SdkWaitId(u64);
73
74impl SdkWaitId {
75 #[must_use]
77 pub const fn new(value: u64) -> Self {
78 Self(value)
79 }
80
81 #[must_use]
83 pub const fn as_u64(self) -> u64 {
84 self.0
85 }
86}
87
88#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
90pub enum Target {
91 Session(SessionName),
93 Window(WindowTarget),
95 Pane(PaneTarget),
97}
98
99impl Target {
100 pub fn parse(value: &str) -> Result<Self, RmuxError> {
102 if let Some((session_name, tail)) = value.split_once(':') {
103 let session_name = SessionName::new(session_name.to_owned())?;
104
105 if !tail.is_empty() && tail.chars().all(|character| character.is_ascii_digit()) {
106 let window_index = parse_window_index(value, tail)?;
107 return Ok(Self::Window(WindowTarget::with_window(
108 session_name,
109 window_index,
110 )));
111 }
112
113 if let Some((window_index, pane_index)) = tail.split_once('.') {
114 let window_index = parse_window_index(value, window_index)?;
115 let pane_index = parse_pane_index(value, pane_index)?;
116 return Ok(Self::Pane(PaneTarget::with_window(
117 session_name,
118 window_index,
119 pane_index,
120 )));
121 }
122
123 return Err(RmuxError::invalid_target(
124 value,
125 "targets must match 'session', 'session:window', or 'session:window.pane'",
126 ));
127 }
128
129 Ok(Self::Session(SessionName::new(value.to_owned())?))
130 }
131
132 #[must_use]
134 pub fn session_name(&self) -> &SessionName {
135 match self {
136 Self::Session(session_name) => session_name,
137 Self::Window(target) => target.session_name(),
138 Self::Pane(target) => target.session_name(),
139 }
140 }
141}
142
143impl fmt::Display for Target {
144 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
145 match self {
146 Self::Session(session_name) => session_name.fmt(formatter),
147 Self::Window(target) => target.fmt(formatter),
148 Self::Pane(target) => target.fmt(formatter),
149 }
150 }
151}
152
153impl FromStr for Target {
154 type Err = RmuxError;
155
156 fn from_str(value: &str) -> Result<Self, Self::Err> {
157 Self::parse(value)
158 }
159}
160
161#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
163pub struct WindowTarget {
164 session_name: SessionName,
165 window_index: u32,
166}
167
168impl WindowTarget {
169 #[must_use]
171 pub const fn new(session_name: SessionName) -> Self {
172 Self::with_window(session_name, 0)
173 }
174
175 #[must_use]
177 pub const fn with_window(session_name: SessionName, window_index: u32) -> Self {
178 Self {
179 session_name,
180 window_index,
181 }
182 }
183
184 #[must_use]
186 pub const fn session_name(&self) -> &SessionName {
187 &self.session_name
188 }
189
190 #[must_use]
192 pub const fn window_index(&self) -> u32 {
193 self.window_index
194 }
195}
196
197impl fmt::Display for WindowTarget {
198 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
199 write!(formatter, "{}:{}", self.session_name, self.window_index)
200 }
201}
202
203#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
205pub struct PaneTarget {
206 session_name: SessionName,
207 window_index: u32,
208 pane_index: u32,
209}
210
211impl PaneTarget {
212 #[must_use]
214 pub const fn new(session_name: SessionName, pane_index: u32) -> Self {
215 Self::with_window(session_name, 0, pane_index)
216 }
217
218 #[must_use]
220 pub const fn with_window(
221 session_name: SessionName,
222 window_index: u32,
223 pane_index: u32,
224 ) -> Self {
225 Self {
226 session_name,
227 window_index,
228 pane_index,
229 }
230 }
231
232 #[must_use]
234 pub const fn session_name(&self) -> &SessionName {
235 &self.session_name
236 }
237
238 #[must_use]
240 pub const fn window_index(&self) -> u32 {
241 self.window_index
242 }
243
244 #[must_use]
246 pub const fn pane_index(&self) -> u32 {
247 self.pane_index
248 }
249}
250
251impl fmt::Display for PaneTarget {
252 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
253 write!(
254 formatter,
255 "{}:{}.{}",
256 self.session_name, self.window_index, self.pane_index
257 )
258 }
259}
260
261#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
263pub enum ScopeSelector {
264 Global,
266 Session(SessionName),
268 Window(WindowTarget),
270 Pane(PaneTarget),
272}
273
274#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
276pub enum OptionScopeSelector {
277 ServerGlobal,
279 SessionGlobal,
281 WindowGlobal,
283 Session(SessionName),
285 Window(WindowTarget),
287 Pane(PaneTarget),
289}
290
291#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
293pub enum LayoutName {
294 MainVertical,
296 MainHorizontal,
298 EvenHorizontal,
300 EvenVertical,
302 Tiled,
304 MainHorizontalMirrored,
306 MainVerticalMirrored,
308}
309
310impl fmt::Display for LayoutName {
311 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
312 match self {
313 Self::MainVertical => formatter.write_str("main-vertical"),
314 Self::MainHorizontal => formatter.write_str("main-horizontal"),
315 Self::EvenHorizontal => formatter.write_str("even-horizontal"),
316 Self::EvenVertical => formatter.write_str("even-vertical"),
317 Self::Tiled => formatter.write_str("tiled"),
318 Self::MainHorizontalMirrored => formatter.write_str("main-horizontal-mirrored"),
319 Self::MainVerticalMirrored => formatter.write_str("main-vertical-mirrored"),
320 }
321 }
322}
323
324impl FromStr for LayoutName {
325 type Err = RmuxError;
326
327 fn from_str(value: &str) -> Result<Self, Self::Err> {
328 match value {
329 "main-vertical" => Ok(Self::MainVertical),
330 "main-horizontal" => Ok(Self::MainHorizontal),
331 "even-horizontal" => Ok(Self::EvenHorizontal),
332 "even-vertical" => Ok(Self::EvenVertical),
333 "tiled" => Ok(Self::Tiled),
334 "main-horizontal-mirrored" => Ok(Self::MainHorizontalMirrored),
335 "main-vertical-mirrored" => Ok(Self::MainVerticalMirrored),
336 _ => Err(RmuxError::Server(format!("unknown layout: {value}"))),
337 }
338 }
339}
340
341#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
350pub enum SplitDirection {
351 #[default]
354 Vertical,
355 Horizontal,
357}
358
359#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
361pub enum ResizePaneAdjustment {
362 AbsoluteWidth {
364 columns: u16,
366 },
367 AbsoluteHeight {
369 rows: u16,
371 },
372 Zoom,
374 Up {
376 cells: u16,
378 },
379 Down {
381 cells: u16,
383 },
384 Left {
386 cells: u16,
388 },
389 Right {
391 cells: u16,
393 },
394 NoOp,
396}
397
398fn parse_pane_index(target: &str, pane_index: &str) -> Result<u32, RmuxError> {
399 if pane_index.is_empty() {
400 return Err(RmuxError::invalid_target(
401 target,
402 "pane index must be an unsigned integer",
403 ));
404 }
405
406 pane_index
407 .parse::<u32>()
408 .map_err(|_| RmuxError::invalid_target(target, "pane index must be an unsigned integer"))
409}
410
411fn parse_window_index(target: &str, window_index: &str) -> Result<u32, RmuxError> {
412 if window_index.is_empty() {
413 return Err(RmuxError::invalid_target(
414 target,
415 "window index must be an unsigned integer",
416 ));
417 }
418
419 window_index
420 .parse::<u32>()
421 .map_err(|_| RmuxError::invalid_target(target, "window index must be an unsigned integer"))
422}
423
424#[cfg(test)]
425mod tests;