tmux_lib/
window_id.rs

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