tmux_layout/tmux/
layout.rs

1use crate::config;
2
3pub use parser::Error;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub enum Layout {
7    Pane(PaneGeom),
8    H(PaneGeom, Vec<Layout>),
9    V(PaneGeom, Vec<Layout>),
10}
11
12impl Layout {
13    pub fn parse(input: &str) -> Result<Layout, Error> {
14        Ok(parser::parse_layout(input)?.1)
15    }
16
17    pub fn geom(&self) -> &PaneGeom {
18        match self {
19            Layout::Pane(geom) => geom,
20            Layout::H(geom, _) => geom,
21            Layout::V(geom, _) => geom,
22        }
23    }
24
25    pub fn width(&self) -> u32 {
26        self.geom().width()
27    }
28
29    pub fn height(&self) -> u32 {
30        self.geom().height()
31    }
32}
33
34impl From<Layout> for config::Split {
35    fn from(split: Layout) -> Self {
36        const LINE_WIDTH: f32 = 1.0;
37
38        match split {
39            Layout::Pane(_) => config::Split::default(),
40            Layout::H(_, mut splits) => {
41                let Some(last_split) = splits.pop() else {
42                    return config::Split::default();
43                };
44
45                let mut acc_width = last_split.width() as f32;
46                let mut acc_split = last_split.into();
47
48                // Build right-associative HSplit by traversing
49                // the splits vector from right-to-left.
50                for left_split in splits.into_iter().rev() {
51                    let new_width = acc_width + left_split.width() as f32 - LINE_WIDTH;
52                    let right_width_percent = (acc_width * 100f32 / new_width).round();
53                    acc_split = config::Split::H {
54                        left: config::HSplitPart {
55                            width: None,
56                            split: Box::new(left_split.into()),
57                        },
58                        right: config::HSplitPart {
59                            width: Some(format!("{:.0}%", right_width_percent)),
60                            split: Box::new(acc_split),
61                        },
62                    };
63                    acc_width = new_width;
64                }
65                acc_split
66            }
67            Layout::V(_, mut splits) => {
68                let Some(last_split) = splits.pop() else {
69                    return config::Split::default();
70                };
71
72                let mut acc_height = last_split.height() as f32;
73                let mut acc_split = last_split.into();
74
75                // Build right-associative VSplit by traversing
76                // the splits vector from right-to-left.
77                for top_split in splits.into_iter().rev() {
78                    let new_height = acc_height + top_split.height() as f32 - LINE_WIDTH;
79                    let bottom_height_percent = (acc_height * 100f32 / new_height).round();
80                    acc_split = config::Split::V {
81                        top: config::VSplitPart {
82                            height: None,
83                            split: Box::new(top_split.into()),
84                        },
85                        bottom: config::VSplitPart {
86                            height: Some(format!("{:.0}%", bottom_height_percent)),
87                            split: Box::new(acc_split),
88                        },
89                    };
90                    acc_height = new_height;
91                }
92                acc_split
93            }
94        }
95    }
96}
97
98#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
99pub struct PaneGeom {
100    pub size: Size,
101    pub x_offset: u32,
102    pub y_offset: u32,
103}
104
105impl PaneGeom {
106    pub fn width(&self) -> u32 {
107        self.size.width
108    }
109
110    pub fn height(&self) -> u32 {
111        self.size.height
112    }
113}
114
115#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
116pub struct Size {
117    pub width: u32,
118    pub height: u32,
119}
120
121impl Size {
122    fn new(width: u32, height: u32) -> Self {
123        Self { width, height }
124    }
125}
126
127mod parser {
128    use nom::{
129        branch::alt,
130        bytes::complete::{tag, take, take_until},
131        character::complete::{digit1, u32},
132        combinator::{all_consuming, map, value},
133        multi::separated_list1,
134        sequence::{delimited, pair, preceded, terminated},
135        IResult, Parser,
136    };
137    use thiserror::Error;
138
139    use super::*;
140
141    pub(super) fn parse_layout(i: I) -> Result<Layout> {
142        all_consuming(layout).parse(i)
143    }
144
145    #[derive(Debug, Error)]
146    pub enum Error {
147        #[error("layout parse error: {0}")]
148        ParseError(String),
149    }
150
151    impl<E: std::error::Error> From<nom::Err<E>> for Error {
152        fn from(err: nom::Err<E>) -> Self {
153            Error::ParseError(format!("{}", err))
154        }
155    }
156
157    type I<'a> = &'a str;
158    type Result<'a, A> = IResult<I<'a>, A>;
159
160    fn layout(i: I) -> Result<Layout> {
161        preceded(checksum, split).parse(i)
162    }
163
164    fn split(i: I) -> Result<Layout> {
165        alt((pane_split, h_split, v_split)).parse(i)
166    }
167
168    fn pane_split(i: I) -> Result<Layout> {
169        map(terminated(pane_geom, pair(tag(","), digit1)), Layout::Pane).parse(i)
170    }
171
172    fn h_split(i: I) -> Result<Layout> {
173        map(
174            pair(
175                pane_geom,
176                delimited(tag("{"), separated_list1(tag(","), split), tag("}")),
177            ),
178            |(pane, splits)| Layout::H(pane, splits),
179        )
180        .parse(i)
181    }
182
183    fn v_split(i: I) -> Result<Layout> {
184        map(
185            pair(
186                pane_geom,
187                delimited(tag("["), separated_list1(tag(","), split), tag("]")),
188            ),
189            |(pane, splits)| Layout::V(pane, splits),
190        )
191        .parse(i)
192    }
193
194    fn pane_geom(i: I) -> Result<PaneGeom> {
195        map(
196            (size, tag(","), u32, tag(","), u32),
197            |(size, _, x_offset, _, y_offset)| PaneGeom {
198                size,
199                x_offset,
200                y_offset,
201            },
202        )
203        .parse(i)
204    }
205
206    fn checksum(i: I) -> Result<()> {
207        value((), (take_until(","), take(1usize))).parse(i)
208    }
209
210    fn size(i: I) -> Result<Size> {
211        map((u32, tag("x"), u32), |(width, _, height)| {
212            Size::new(width, height)
213        })
214        .parse(i)
215    }
216}
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221
222    #[test]
223    fn sample1() {
224        let sample1 = "4264,401x112,0,0{200x112,0,0[200x56,0,0,546,200x55,0,57,798],200x112,201,0[200x56,201,0,795,200x55,201,57{100x55,201,57,796,99x55,302,57[99x27,302,57,797,99x27,302,85,799]}]}";
225        let layout = Layout::parse(sample1).unwrap();
226
227        use Layout::*;
228
229        assert_eq!(
230            layout,
231            H(
232                PaneGeom {
233                    size: Size {
234                        width: 401,
235                        height: 112,
236                    },
237                    x_offset: 0,
238                    y_offset: 0,
239                },
240                vec![
241                    V(
242                        PaneGeom {
243                            size: Size {
244                                width: 200,
245                                height: 112,
246                            },
247                            x_offset: 0,
248                            y_offset: 0,
249                        },
250                        vec![
251                            Pane(PaneGeom {
252                                size: Size {
253                                    width: 200,
254                                    height: 56,
255                                },
256                                x_offset: 0,
257                                y_offset: 0,
258                            },),
259                            Pane(PaneGeom {
260                                size: Size {
261                                    width: 200,
262                                    height: 55,
263                                },
264                                x_offset: 0,
265                                y_offset: 57,
266                            },),
267                        ],
268                    ),
269                    V(
270                        PaneGeom {
271                            size: Size {
272                                width: 200,
273                                height: 112,
274                            },
275                            x_offset: 201,
276                            y_offset: 0,
277                        },
278                        vec![
279                            Pane(PaneGeom {
280                                size: Size {
281                                    width: 200,
282                                    height: 56,
283                                },
284                                x_offset: 201,
285                                y_offset: 0,
286                            },),
287                            H(
288                                PaneGeom {
289                                    size: Size {
290                                        width: 200,
291                                        height: 55,
292                                    },
293                                    x_offset: 201,
294                                    y_offset: 57,
295                                },
296                                vec![
297                                    Pane(PaneGeom {
298                                        size: Size {
299                                            width: 100,
300                                            height: 55,
301                                        },
302                                        x_offset: 201,
303                                        y_offset: 57,
304                                    },),
305                                    V(
306                                        PaneGeom {
307                                            size: Size {
308                                                width: 99,
309                                                height: 55,
310                                            },
311                                            x_offset: 302,
312                                            y_offset: 57,
313                                        },
314                                        vec![
315                                            Pane(PaneGeom {
316                                                size: Size {
317                                                    width: 99,
318                                                    height: 27,
319                                                },
320                                                x_offset: 302,
321                                                y_offset: 57,
322                                            },),
323                                            Pane(PaneGeom {
324                                                size: Size {
325                                                    width: 99,
326                                                    height: 27,
327                                                },
328                                                x_offset: 302,
329                                                y_offset: 85,
330                                            },),
331                                        ],
332                                    ),
333                                ],
334                            ),
335                        ],
336                    ),
337                ],
338            )
339        );
340    }
341}