1use 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#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
20pub struct PaneId(pub String);
21
22impl FromStr for PaneId {
23 type Err = Error;
24
25 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 #[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 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 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}