1use nom::{
15 branch::alt,
16 character::complete::{char, digit1, hex_digit1},
17 combinator::{all_consuming, map_res},
18 multi::separated_list1,
19 sequence::{delimited, tuple},
20 IResult,
21};
22
23use crate::{error::map_add_intent, Result};
24
25#[derive(Debug, PartialEq, Eq)]
27pub struct WindowLayout {
28 id: u16,
30 container: Container,
32}
33
34impl WindowLayout {
35 #[must_use]
37 pub fn pane_ids(&self) -> Vec<u16> {
38 let mut acc: Vec<u16> = Vec::with_capacity(1);
39 self.walk(&mut acc);
40 acc
41 }
42
43 fn walk(&self, acc: &mut Vec<u16>) {
45 self.container.walk(acc);
46 }
47}
48
49#[derive(Debug, PartialEq, Eq)]
50struct Container {
51 dimensions: Dimensions,
53 coordinates: Coordinates,
55 element: Element,
57}
58
59impl Container {
60 fn walk(&self, acc: &mut Vec<u16>) {
62 self.element.walk(acc);
63 }
64}
65
66#[derive(Debug, PartialEq, Eq)]
67struct Dimensions {
68 width: u16,
70 height: u16,
72}
73
74#[derive(Debug, PartialEq, Eq)]
75struct Coordinates {
76 x: u16,
78 y: u16,
80}
81
82#[derive(Debug, PartialEq, Eq)]
84enum Element {
85 Pane { pane_id: u16 },
87 Horizontal(Split),
89 Vertical(Split),
91}
92
93impl Element {
94 fn walk(&self, acc: &mut Vec<u16>) {
96 match self {
97 Self::Pane { pane_id } => acc.push(*pane_id),
98 Self::Horizontal(split) | Self::Vertical(split) => {
99 split.walk(acc);
100 }
101 }
102 }
103}
104
105#[derive(Debug, PartialEq, Eq)]
106struct Split {
107 elements: Vec<Container>,
109}
110
111impl Split {
112 fn walk(&self, acc: &mut Vec<u16>) {
114 for element in &self.elements {
115 element.walk(acc);
116 }
117 }
118}
119
120pub fn parse_window_layout(input: &str) -> Result<WindowLayout> {
122 let desc = "window-layout";
123 let intent = "window-layout";
124 let (_, win_layout) =
125 all_consuming(window_layout)(input).map_err(|e| map_add_intent(desc, intent, e))?;
126
127 Ok(win_layout)
128}
129
130pub(crate) fn window_layout(input: &str) -> IResult<&str, WindowLayout> {
131 let (input, (id, _, container)) = tuple((layout_id, char(','), container))(input)?;
132 Ok((input, WindowLayout { id, container }))
133}
134
135fn from_hex(input: &str) -> std::result::Result<u16, std::num::ParseIntError> {
136 u16::from_str_radix(input, 16)
137}
138
139fn layout_id(input: &str) -> IResult<&str, u16> {
140 map_res(hex_digit1, from_hex)(input)
141}
142
143fn parse_u16(input: &str) -> IResult<&str, u16> {
144 map_res(digit1, str::parse)(input)
145}
146
147fn dimensions(input: &str) -> IResult<&str, Dimensions> {
148 let (input, (width, _, height)) = tuple((parse_u16, char('x'), parse_u16))(input)?;
149 Ok((input, Dimensions { width, height }))
150}
151
152fn coordinates(input: &str) -> IResult<&str, Coordinates> {
153 let (input, (x, _, y)) = tuple((parse_u16, char(','), parse_u16))(input)?;
154 Ok((input, Coordinates { x, y }))
155}
156
157fn single_pane(input: &str) -> IResult<&str, Element> {
158 let (input, (_, pane_id)) = tuple((char(','), parse_u16))(input)?;
159 Ok((input, Element::Pane { pane_id }))
160}
161
162fn horiz_split(input: &str) -> IResult<&str, Element> {
163 let (input, elements) =
164 delimited(char('{'), separated_list1(char(','), container), char('}'))(input)?;
165 Ok((input, Element::Horizontal(Split { elements })))
166}
167
168fn vert_split(input: &str) -> IResult<&str, Element> {
169 let (input, elements) =
170 delimited(char('['), separated_list1(char(','), container), char(']'))(input)?;
171 Ok((input, Element::Vertical(Split { elements })))
172}
173
174fn element(input: &str) -> IResult<&str, Element> {
175 alt((single_pane, horiz_split, vert_split))(input)
176}
177
178fn container(input: &str) -> IResult<&str, Container> {
179 let (input, (dimensions, _, coordinates, element)) =
180 tuple((dimensions, char(','), coordinates, element))(input)?;
181 Ok((
182 input,
183 Container {
184 dimensions,
185 coordinates,
186 element,
187 },
188 ))
189}
190
191#[cfg(test)]
192mod tests {
193
194 use super::{
195 coordinates, dimensions, layout_id, single_pane, vert_split, window_layout, Container,
196 Coordinates, Dimensions, Element, Split, WindowLayout,
197 };
198
199 #[test]
200 fn test_parse_layout_id() {
201 let input = "9f58";
202
203 let actual = layout_id(input);
204 let expected = Ok(("", 40792_u16));
205 assert_eq!(actual, expected);
206 }
207
208 #[test]
209 fn test_parse_dimensions() {
210 let input = "237x0";
211
212 let actual = dimensions(input);
213 let expected = Ok((
214 "",
215 Dimensions {
216 width: 237,
217 height: 0,
218 },
219 ));
220 assert_eq!(actual, expected);
221
222 let input = "7x13";
223
224 let actual = dimensions(input);
225 let expected = Ok((
226 "",
227 Dimensions {
228 width: 7,
229 height: 13,
230 },
231 ));
232 assert_eq!(actual, expected);
233 }
234
235 #[test]
236 fn test_parse_coordinates() {
237 let input = "120,0";
238
239 let actual = coordinates(input);
240 let expected = Ok(("", Coordinates { x: 120, y: 0 }));
241 assert_eq!(actual, expected);
242 }
243
244 #[test]
245 fn test_single_pane() {
246 let input = ",46";
247
248 let actual = single_pane(input);
249 let expected = Ok(("", Element::Pane { pane_id: 46 }));
250 assert_eq!(actual, expected);
251 }
252
253 #[test]
254 fn test_vertical_split() {
255 let input = "[279x47,0,0,82,279x23,0,48,83]";
256
257 let actual = vert_split(input);
258 let expected = Ok((
259 "",
260 Element::Vertical(Split {
261 elements: vec![
262 Container {
263 dimensions: Dimensions {
264 width: 279,
265 height: 47,
266 },
267 coordinates: Coordinates { x: 0, y: 0 },
268 element: Element::Pane { pane_id: 82 },
269 },
270 Container {
271 dimensions: Dimensions {
272 width: 279,
273 height: 23,
274 },
275 coordinates: Coordinates { x: 0, y: 48 },
276 element: Element::Pane { pane_id: 83 },
277 },
278 ],
279 }),
280 ));
281 assert_eq!(actual, expected);
282 }
283
284 #[test]
285 fn test_layout() {
286 let input = "41e9,279x71,0,0[279x40,0,0,71,279x30,0,41{147x30,0,41,72,131x30,148,41,73}]";
287
288 let actual = window_layout(input);
289 let expected = Ok((
290 "",
291 WindowLayout {
292 id: 0x41e9,
293 container: Container {
294 dimensions: Dimensions {
295 width: 279,
296 height: 71,
297 },
298 coordinates: Coordinates { x: 0, y: 0 },
299 element: Element::Vertical(Split {
300 elements: vec![
301 Container {
302 dimensions: Dimensions {
303 width: 279,
304 height: 40,
305 },
306 coordinates: Coordinates { x: 0, y: 0 },
307 element: Element::Pane { pane_id: 71 },
308 },
309 Container {
310 dimensions: Dimensions {
311 width: 279,
312 height: 30,
313 },
314 coordinates: Coordinates { x: 0, y: 41 },
315 element: Element::Horizontal(Split {
316 elements: vec![
317 Container {
318 dimensions: Dimensions {
319 width: 147,
320 height: 30,
321 },
322 coordinates: Coordinates { x: 0, y: 41 },
323 element: Element::Pane { pane_id: 72 },
324 },
325 Container {
326 dimensions: Dimensions {
327 width: 131,
328 height: 30,
329 },
330 coordinates: Coordinates { x: 148, y: 41 },
331 element: Element::Pane { pane_id: 73 },
332 },
333 ],
334 }),
335 },
336 ],
337 }),
338 },
339 },
340 ));
341 assert_eq!(actual, expected);
342 }
343
344 #[test]
345 fn test_pane_ids() {
346 let input = "41e9,279x71,0,0[279x40,0,0,71,279x30,0,41{147x30,0,41,72,131x30,148,41,73}]";
347 let (_, layout) = window_layout(input).unwrap();
348
349 let actual = layout.pane_ids();
350 let expected = vec![71, 72, 73];
351 assert_eq!(actual, expected);
352 }
353}