tmux_lib/
pane_id.rs

1//! Pane id.
2
3use std::fmt;
4use std::str::FromStr;
5
6use nom::{
7    character::complete::{char, digit1},
8    combinator::all_consuming,
9    sequence::preceded,
10    IResult, Parser,
11};
12use serde::{Deserialize, Serialize};
13
14use crate::error::{map_add_intent, Error};
15
16/// The id of a Tmux pane.
17///
18/// This wraps the raw tmux representation (`%12`).
19#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
20pub struct PaneId(pub String);
21
22impl FromStr for PaneId {
23    type Err = Error;
24
25    /// Parse into `PaneId`. The `&str` must start with '%' followed by a `u32`.
26    fn from_str(input: &str) -> Result<Self, Self::Err> {
27        let desc = "PaneId";
28        let intent = "##{pane_id}";
29
30        let (_, pane_id) = all_consuming(parse::pane_id)
31            .parse(input)
32            .map_err(|e| map_add_intent(desc, intent, e))?;
33
34        Ok(pane_id)
35    }
36}
37
38impl From<&u16> for PaneId {
39    fn from(value: &u16) -> Self {
40        Self(format!("%{value}"))
41    }
42}
43
44impl PaneId {
45    /// Extract a string slice containing the raw representation.
46    #[must_use]
47    pub fn as_str(&self) -> &str {
48        &self.0
49    }
50}
51
52impl fmt::Display for PaneId {
53    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54        write!(f, "{}", self.0)
55    }
56}
57
58pub(crate) mod parse {
59    use super::{char, digit1, preceded, IResult, PaneId, Parser};
60
61    pub(crate) fn pane_id(input: &str) -> IResult<&str, PaneId> {
62        let (input, digit) = preceded(char('%'), digit1).parse(input)?;
63        let id = format!("%{digit}");
64        Ok((input, PaneId(id)))
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn test_parse_pane_id_fn() {
74        let actual = parse::pane_id("%43");
75        let expected = Ok(("", PaneId("%43".into())));
76        assert_eq!(actual, expected);
77
78        let actual = parse::pane_id("%4");
79        let expected = Ok(("", PaneId("%4".into())));
80        assert_eq!(actual, expected);
81    }
82
83    #[test]
84    fn test_parse_pane_id_struct() {
85        let actual = PaneId::from_str("%43");
86        assert!(actual.is_ok());
87        assert_eq!(actual.unwrap(), PaneId("%43".into()));
88
89        let actual = PaneId::from_str("4:38");
90        assert!(matches!(
91            actual,
92            Err(Error::ParseError {
93                desc: "PaneId",
94                intent: "##{pane_id}",
95                err: _
96            })
97        ));
98    }
99
100    #[test]
101    fn test_parse_pane_id_with_large_number() {
102        let pane_id = PaneId::from_str("%99999").unwrap();
103        assert_eq!(pane_id.as_str(), "%99999");
104    }
105
106    #[test]
107    fn test_parse_pane_id_fails_on_wrong_prefix() {
108        // @ is for window, $ is for session
109        assert!(PaneId::from_str("@1").is_err());
110        assert!(PaneId::from_str("$1").is_err());
111    }
112
113    #[test]
114    fn test_parse_pane_id_fails_on_no_prefix() {
115        assert!(PaneId::from_str("123").is_err());
116    }
117
118    #[test]
119    fn test_parse_pane_id_fails_on_empty() {
120        assert!(PaneId::from_str("").is_err());
121        assert!(PaneId::from_str("%").is_err());
122    }
123
124    #[test]
125    fn test_parse_pane_id_fails_on_non_numeric() {
126        assert!(PaneId::from_str("%abc").is_err());
127        assert!(PaneId::from_str("%12abc").is_err());
128    }
129
130    #[test]
131    fn test_parse_pane_id_fails_on_extra_content() {
132        // all_consuming should reject trailing content
133        assert!(PaneId::from_str("%12:extra").is_err());
134    }
135
136    #[test]
137    fn test_pane_id_as_str() {
138        let pane_id = PaneId::from_str("%42").unwrap();
139        assert_eq!(pane_id.as_str(), "%42");
140    }
141
142    #[test]
143    fn test_pane_id_display() {
144        let pane_id = PaneId::from_str("%42").unwrap();
145        assert_eq!(format!("{}", pane_id), "%42");
146    }
147
148    #[test]
149    fn test_pane_id_from_u16() {
150        let pane_id = PaneId::from(&42u16);
151        assert_eq!(pane_id.as_str(), "%42");
152    }
153
154    #[test]
155    fn test_pane_id_from_u16_zero() {
156        let pane_id = PaneId::from(&0u16);
157        assert_eq!(pane_id.as_str(), "%0");
158    }
159}