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