use nom::{
IResult, Parser,
branch::alt,
character::complete::{char, digit1, hex_digit1},
combinator::{all_consuming, map_res},
multi::separated_list1,
sequence::delimited,
};
use crate::{Result, error::map_add_intent};
#[derive(Debug, PartialEq, Eq)]
pub struct WindowLayout {
id: u16,
container: Container,
}
impl WindowLayout {
#[must_use]
pub fn pane_ids(&self) -> Vec<u16> {
let mut acc: Vec<u16> = Vec::with_capacity(1);
self.walk(&mut acc);
acc
}
fn walk(&self, acc: &mut Vec<u16>) {
self.container.walk(acc);
}
}
#[derive(Debug, PartialEq, Eq)]
struct Container {
dimensions: Dimensions,
coordinates: Coordinates,
element: Element,
}
impl Container {
fn walk(&self, acc: &mut Vec<u16>) {
self.element.walk(acc);
}
}
#[derive(Debug, PartialEq, Eq)]
struct Dimensions {
width: u16,
height: u16,
}
#[derive(Debug, PartialEq, Eq)]
struct Coordinates {
x: u16,
y: u16,
}
#[derive(Debug, PartialEq, Eq)]
enum Element {
Pane { pane_id: u16 },
Horizontal(Split),
Vertical(Split),
}
impl Element {
fn walk(&self, acc: &mut Vec<u16>) {
match self {
Self::Pane { pane_id } => acc.push(*pane_id),
Self::Horizontal(split) | Self::Vertical(split) => {
split.walk(acc);
}
}
}
}
#[derive(Debug, PartialEq, Eq)]
struct Split {
elements: Vec<Container>,
}
impl Split {
fn walk(&self, acc: &mut Vec<u16>) {
for element in &self.elements {
element.walk(acc);
}
}
}
pub fn parse_window_layout(input: &str) -> Result<WindowLayout> {
let desc = "window-layout";
let intent = "window-layout";
let (_, win_layout) = all_consuming(window_layout)
.parse(input)
.map_err(|e| map_add_intent(desc, intent, e))?;
Ok(win_layout)
}
pub(crate) fn window_layout(input: &str) -> IResult<&str, WindowLayout> {
let (input, (id, _, container)) = (layout_id, char(','), container).parse(input)?;
Ok((input, WindowLayout { id, container }))
}
fn from_hex(input: &str) -> std::result::Result<u16, std::num::ParseIntError> {
u16::from_str_radix(input, 16)
}
fn layout_id(input: &str) -> IResult<&str, u16> {
map_res(hex_digit1, from_hex).parse(input)
}
fn parse_u16(input: &str) -> IResult<&str, u16> {
map_res(digit1, str::parse).parse(input)
}
fn dimensions(input: &str) -> IResult<&str, Dimensions> {
let (input, (width, _, height)) = (parse_u16, char('x'), parse_u16).parse(input)?;
Ok((input, Dimensions { width, height }))
}
fn coordinates(input: &str) -> IResult<&str, Coordinates> {
let (input, (x, _, y)) = (parse_u16, char(','), parse_u16).parse(input)?;
Ok((input, Coordinates { x, y }))
}
fn single_pane(input: &str) -> IResult<&str, Element> {
let (input, (_, pane_id)) = (char(','), parse_u16).parse(input)?;
Ok((input, Element::Pane { pane_id }))
}
fn horiz_split(input: &str) -> IResult<&str, Element> {
let (input, elements) =
delimited(char('{'), separated_list1(char(','), container), char('}')).parse(input)?;
Ok((input, Element::Horizontal(Split { elements })))
}
fn vert_split(input: &str) -> IResult<&str, Element> {
let (input, elements) =
delimited(char('['), separated_list1(char(','), container), char(']')).parse(input)?;
Ok((input, Element::Vertical(Split { elements })))
}
fn element(input: &str) -> IResult<&str, Element> {
alt((single_pane, horiz_split, vert_split)).parse(input)
}
fn container(input: &str) -> IResult<&str, Container> {
let (input, (dimensions, _, coordinates, element)) =
(dimensions, char(','), coordinates, element).parse(input)?;
Ok((
input,
Container {
dimensions,
coordinates,
element,
},
))
}
#[cfg(test)]
mod tests {
use super::{
Container, Coordinates, Dimensions, Element, Split, WindowLayout, coordinates, dimensions,
layout_id, single_pane, vert_split, window_layout,
};
#[test]
fn test_parse_layout_id() {
let input = "9f58";
let actual = layout_id(input);
let expected = Ok(("", 40792_u16));
assert_eq!(actual, expected);
}
#[test]
fn test_parse_dimensions() {
let input = "237x0";
let actual = dimensions(input);
let expected = Ok((
"",
Dimensions {
width: 237,
height: 0,
},
));
assert_eq!(actual, expected);
let input = "7x13";
let actual = dimensions(input);
let expected = Ok((
"",
Dimensions {
width: 7,
height: 13,
},
));
assert_eq!(actual, expected);
}
#[test]
fn test_parse_coordinates() {
let input = "120,0";
let actual = coordinates(input);
let expected = Ok(("", Coordinates { x: 120, y: 0 }));
assert_eq!(actual, expected);
}
#[test]
fn test_single_pane() {
let input = ",46";
let actual = single_pane(input);
let expected = Ok(("", Element::Pane { pane_id: 46 }));
assert_eq!(actual, expected);
}
#[test]
fn test_vertical_split() {
let input = "[279x47,0,0,82,279x23,0,48,83]";
let actual = vert_split(input);
let expected = Ok((
"",
Element::Vertical(Split {
elements: vec![
Container {
dimensions: Dimensions {
width: 279,
height: 47,
},
coordinates: Coordinates { x: 0, y: 0 },
element: Element::Pane { pane_id: 82 },
},
Container {
dimensions: Dimensions {
width: 279,
height: 23,
},
coordinates: Coordinates { x: 0, y: 48 },
element: Element::Pane { pane_id: 83 },
},
],
}),
));
assert_eq!(actual, expected);
}
#[test]
fn test_layout() {
let input = "41e9,279x71,0,0[279x40,0,0,71,279x30,0,41{147x30,0,41,72,131x30,148,41,73}]";
let actual = window_layout(input);
let expected = Ok((
"",
WindowLayout {
id: 0x41e9,
container: Container {
dimensions: Dimensions {
width: 279,
height: 71,
},
coordinates: Coordinates { x: 0, y: 0 },
element: Element::Vertical(Split {
elements: vec![
Container {
dimensions: Dimensions {
width: 279,
height: 40,
},
coordinates: Coordinates { x: 0, y: 0 },
element: Element::Pane { pane_id: 71 },
},
Container {
dimensions: Dimensions {
width: 279,
height: 30,
},
coordinates: Coordinates { x: 0, y: 41 },
element: Element::Horizontal(Split {
elements: vec![
Container {
dimensions: Dimensions {
width: 147,
height: 30,
},
coordinates: Coordinates { x: 0, y: 41 },
element: Element::Pane { pane_id: 72 },
},
Container {
dimensions: Dimensions {
width: 131,
height: 30,
},
coordinates: Coordinates { x: 148, y: 41 },
element: Element::Pane { pane_id: 73 },
},
],
}),
},
],
}),
},
},
));
assert_eq!(actual, expected);
}
#[test]
fn test_pane_ids() {
let input = "41e9,279x71,0,0[279x40,0,0,71,279x30,0,41{147x30,0,41,72,131x30,148,41,73}]";
let (_, layout) = window_layout(input).unwrap();
let actual = layout.pane_ids();
let expected = vec![71, 72, 73];
assert_eq!(actual, expected);
}
}