1use nom::{
15 IResult, Parser,
16 branch::alt,
17 character::complete::{char, digit1, hex_digit1},
18 combinator::{all_consuming, map_res},
19 multi::separated_list1,
20 sequence::delimited,
21};
22
23use crate::{Result, error::map_add_intent};
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> {
129 let desc = "window-layout";
130 let intent = "window-layout";
131 let (_, win_layout) = all_consuming(window_layout)
132 .parse(input)
133 .map_err(|e| map_add_intent(desc, intent, e))?;
134
135 Ok(win_layout)
136}
137
138pub(crate) fn window_layout(input: &str) -> IResult<&str, WindowLayout> {
139 let (input, (id, _, container)) = (layout_id, char(','), container).parse(input)?;
140 Ok((input, WindowLayout { id, container }))
141}
142
143fn from_hex(input: &str) -> std::result::Result<u16, std::num::ParseIntError> {
144 u16::from_str_radix(input, 16)
145}
146
147fn layout_id(input: &str) -> IResult<&str, u16> {
148 map_res(hex_digit1, from_hex).parse(input)
149}
150
151fn parse_u16(input: &str) -> IResult<&str, u16> {
152 map_res(digit1, str::parse).parse(input)
153}
154
155fn dimensions(input: &str) -> IResult<&str, Dimensions> {
156 let (input, (width, _, height)) = (parse_u16, char('x'), parse_u16).parse(input)?;
157 Ok((input, Dimensions { width, height }))
158}
159
160fn coordinates(input: &str) -> IResult<&str, Coordinates> {
161 let (input, (x, _, y)) = (parse_u16, char(','), parse_u16).parse(input)?;
162 Ok((input, Coordinates { x, y }))
163}
164
165fn single_pane(input: &str) -> IResult<&str, Element> {
166 let (input, (_, pane_id)) = (char(','), parse_u16).parse(input)?;
167 Ok((input, Element::Pane { pane_id }))
168}
169
170fn horiz_split(input: &str) -> IResult<&str, Element> {
171 let (input, elements) =
172 delimited(char('{'), separated_list1(char(','), container), char('}')).parse(input)?;
173 Ok((input, Element::Horizontal(Split { elements })))
174}
175
176fn vert_split(input: &str) -> IResult<&str, Element> {
177 let (input, elements) =
178 delimited(char('['), separated_list1(char(','), container), char(']')).parse(input)?;
179 Ok((input, Element::Vertical(Split { elements })))
180}
181
182fn element(input: &str) -> IResult<&str, Element> {
183 alt((single_pane, horiz_split, vert_split)).parse(input)
184}
185
186fn container(input: &str) -> IResult<&str, Container> {
187 let (input, (dimensions, _, coordinates, element)) =
188 (dimensions, char(','), coordinates, element).parse(input)?;
189 Ok((
190 input,
191 Container {
192 dimensions,
193 coordinates,
194 element,
195 },
196 ))
197}
198
199#[cfg(test)]
200mod tests {
201
202 use super::{
203 Container, Coordinates, Dimensions, Element, Split, WindowLayout, coordinates, dimensions,
204 layout_id, single_pane, vert_split, window_layout,
205 };
206
207 #[test]
208 fn test_parse_layout_id() {
209 let input = "9f58";
210
211 let actual = layout_id(input);
212 let expected = Ok(("", 40792_u16));
213 assert_eq!(actual, expected);
214 }
215
216 #[test]
217 fn test_parse_dimensions() {
218 let input = "237x0";
219
220 let actual = dimensions(input);
221 let expected = Ok((
222 "",
223 Dimensions {
224 width: 237,
225 height: 0,
226 },
227 ));
228 assert_eq!(actual, expected);
229
230 let input = "7x13";
231
232 let actual = dimensions(input);
233 let expected = Ok((
234 "",
235 Dimensions {
236 width: 7,
237 height: 13,
238 },
239 ));
240 assert_eq!(actual, expected);
241 }
242
243 #[test]
244 fn test_parse_coordinates() {
245 let input = "120,0";
246
247 let actual = coordinates(input);
248 let expected = Ok(("", Coordinates { x: 120, y: 0 }));
249 assert_eq!(actual, expected);
250 }
251
252 #[test]
253 fn test_single_pane() {
254 let input = ",46";
255
256 let actual = single_pane(input);
257 let expected = Ok(("", Element::Pane { pane_id: 46 }));
258 assert_eq!(actual, expected);
259 }
260
261 #[test]
262 fn test_vertical_split() {
263 let input = "[279x47,0,0,82,279x23,0,48,83]";
264
265 let actual = vert_split(input);
266 let expected = Ok((
267 "",
268 Element::Vertical(Split {
269 elements: vec![
270 Container {
271 dimensions: Dimensions {
272 width: 279,
273 height: 47,
274 },
275 coordinates: Coordinates { x: 0, y: 0 },
276 element: Element::Pane { pane_id: 82 },
277 },
278 Container {
279 dimensions: Dimensions {
280 width: 279,
281 height: 23,
282 },
283 coordinates: Coordinates { x: 0, y: 48 },
284 element: Element::Pane { pane_id: 83 },
285 },
286 ],
287 }),
288 ));
289 assert_eq!(actual, expected);
290 }
291
292 #[test]
293 fn test_layout() {
294 let input = "41e9,279x71,0,0[279x40,0,0,71,279x30,0,41{147x30,0,41,72,131x30,148,41,73}]";
295
296 let actual = window_layout(input);
297 let expected = Ok((
298 "",
299 WindowLayout {
300 id: 0x41e9,
301 container: Container {
302 dimensions: Dimensions {
303 width: 279,
304 height: 71,
305 },
306 coordinates: Coordinates { x: 0, y: 0 },
307 element: Element::Vertical(Split {
308 elements: vec![
309 Container {
310 dimensions: Dimensions {
311 width: 279,
312 height: 40,
313 },
314 coordinates: Coordinates { x: 0, y: 0 },
315 element: Element::Pane { pane_id: 71 },
316 },
317 Container {
318 dimensions: Dimensions {
319 width: 279,
320 height: 30,
321 },
322 coordinates: Coordinates { x: 0, y: 41 },
323 element: Element::Horizontal(Split {
324 elements: vec![
325 Container {
326 dimensions: Dimensions {
327 width: 147,
328 height: 30,
329 },
330 coordinates: Coordinates { x: 0, y: 41 },
331 element: Element::Pane { pane_id: 72 },
332 },
333 Container {
334 dimensions: Dimensions {
335 width: 131,
336 height: 30,
337 },
338 coordinates: Coordinates { x: 148, y: 41 },
339 element: Element::Pane { pane_id: 73 },
340 },
341 ],
342 }),
343 },
344 ],
345 }),
346 },
347 },
348 ));
349 assert_eq!(actual, expected);
350 }
351
352 #[test]
353 fn test_pane_ids() {
354 let input = "41e9,279x71,0,0[279x40,0,0,71,279x30,0,41{147x30,0,41,72,131x30,148,41,73}]";
355 let (_, layout) = window_layout(input).unwrap();
356
357 let actual = layout.pane_ids();
358 let expected = vec![71, 72, 73];
359 assert_eq!(actual, expected);
360 }
361}