Skip to main content

rmux_core/
box_lines.rs

1//! tmux-compatible popup and menu border line styles.
2
3/// Border drawing variants accepted by tmux `*-border-lines` options.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
5pub enum BoxLines {
6    /// Single-line Unicode borders.
7    #[default]
8    Single,
9    /// Double-line Unicode borders.
10    Double,
11    /// Heavy Unicode borders.
12    Heavy,
13    /// ASCII borders.
14    Simple,
15    /// Rounded corners with single-line sides.
16    Rounded,
17    /// Whitespace-only borders.
18    Padded,
19    /// No border.
20    None,
21}
22
23impl BoxLines {
24    /// Parses tmux option text into a border variant.
25    #[must_use]
26    pub fn parse(value: Option<&str>) -> Self {
27        match value.unwrap_or("single") {
28            "double" => Self::Double,
29            "heavy" => Self::Heavy,
30            "simple" => Self::Simple,
31            "rounded" => Self::Rounded,
32            "padded" => Self::Padded,
33            "none" => Self::None,
34            _ => Self::Single,
35        }
36    }
37
38    /// Returns whether this border style occupies visible cells.
39    #[must_use]
40    pub const fn visible(self) -> bool {
41        !matches!(self, Self::None)
42    }
43
44    /// Returns the vertical border glyph.
45    #[must_use]
46    pub const fn vertical(self) -> char {
47        match self {
48            Self::Single | Self::Rounded => '│',
49            Self::Double => '║',
50            Self::Heavy => '┃',
51            Self::Simple => '|',
52            Self::Padded | Self::None => ' ',
53        }
54    }
55
56    /// Returns the horizontal border glyph.
57    #[must_use]
58    pub const fn horizontal(self) -> char {
59        match self {
60            Self::Single | Self::Rounded => '─',
61            Self::Double => '═',
62            Self::Heavy => '━',
63            Self::Simple => '-',
64            Self::Padded | Self::None => ' ',
65        }
66    }
67
68    /// Returns the top-left corner glyph.
69    #[must_use]
70    pub const fn top_left(self) -> char {
71        match self {
72            Self::Single => '┌',
73            Self::Double => '╔',
74            Self::Heavy => '┏',
75            Self::Simple => '+',
76            Self::Rounded => '╭',
77            Self::Padded | Self::None => ' ',
78        }
79    }
80
81    /// Returns the top-right corner glyph.
82    #[must_use]
83    pub const fn top_right(self) -> char {
84        match self {
85            Self::Single => '┐',
86            Self::Double => '╗',
87            Self::Heavy => '┓',
88            Self::Simple => '+',
89            Self::Rounded => '╮',
90            Self::Padded | Self::None => ' ',
91        }
92    }
93
94    /// Returns the bottom-left corner glyph.
95    #[must_use]
96    pub const fn bottom_left(self) -> char {
97        match self {
98            Self::Single => '└',
99            Self::Double => '╚',
100            Self::Heavy => '┗',
101            Self::Simple => '+',
102            Self::Rounded => '╰',
103            Self::Padded | Self::None => ' ',
104        }
105    }
106
107    /// Returns the bottom-right corner glyph.
108    #[must_use]
109    pub const fn bottom_right(self) -> char {
110        match self {
111            Self::Single => '┘',
112            Self::Double => '╝',
113            Self::Heavy => '┛',
114            Self::Simple => '+',
115            Self::Rounded => '╯',
116            Self::Padded | Self::None => ' ',
117        }
118    }
119
120    /// Returns the top separator join glyph.
121    #[must_use]
122    pub const fn top_join(self) -> char {
123        match self {
124            Self::Single => '┬',
125            Self::Double => '╦',
126            Self::Heavy | Self::Rounded => '┳',
127            Self::Simple => '+',
128            Self::Padded | Self::None => ' ',
129        }
130    }
131
132    /// Returns the bottom separator join glyph.
133    #[must_use]
134    pub const fn bottom_join(self) -> char {
135        match self {
136            Self::Single => '┴',
137            Self::Double => '╩',
138            Self::Heavy | Self::Rounded => '┻',
139            Self::Simple => '+',
140            Self::Padded | Self::None => ' ',
141        }
142    }
143
144    /// Returns the left separator join glyph.
145    #[must_use]
146    pub const fn left_join(self) -> char {
147        match self {
148            Self::Single | Self::Rounded => '├',
149            Self::Double => '╠',
150            Self::Heavy => '┣',
151            Self::Simple => '+',
152            Self::Padded | Self::None => ' ',
153        }
154    }
155
156    /// Returns the right separator join glyph.
157    #[must_use]
158    pub const fn right_join(self) -> char {
159        match self {
160            Self::Single | Self::Rounded => '┤',
161            Self::Double => '╣',
162            Self::Heavy => '┫',
163            Self::Simple => '+',
164            Self::Padded | Self::None => ' ',
165        }
166    }
167
168    /// Returns the four-way separator join glyph.
169    #[must_use]
170    pub const fn join(self) -> char {
171        match self {
172            Self::Single => '┼',
173            Self::Double => '╬',
174            Self::Heavy | Self::Rounded => '╋',
175            Self::Simple => '+',
176            Self::Padded | Self::None => ' ',
177        }
178    }
179
180    /// Returns the tmux outside-cell glyph for this border style.
181    #[must_use]
182    pub const fn outside(self) -> char {
183        match self {
184            Self::Single | Self::Double | Self::Heavy | Self::Rounded => '·',
185            Self::Simple => '.',
186            Self::Padded | Self::None => ' ',
187        }
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use super::BoxLines;
194
195    #[test]
196    fn parses_tmux_popup_border_choices() {
197        assert_eq!(BoxLines::parse(None), BoxLines::Single);
198        assert_eq!(BoxLines::parse(Some("double")), BoxLines::Double);
199        assert_eq!(BoxLines::parse(Some("heavy")), BoxLines::Heavy);
200        assert_eq!(BoxLines::parse(Some("simple")), BoxLines::Simple);
201        assert_eq!(BoxLines::parse(Some("rounded")), BoxLines::Rounded);
202        assert_eq!(BoxLines::parse(Some("padded")), BoxLines::Padded);
203        assert_eq!(BoxLines::parse(Some("none")), BoxLines::None);
204        assert_eq!(BoxLines::parse(Some("unknown")), BoxLines::Single);
205    }
206
207    #[test]
208    fn exposes_tmux_join_glyphs_for_all_border_sets() {
209        assert_eq!(BoxLines::Single.top_join(), '┬');
210        assert_eq!(BoxLines::Single.bottom_join(), '┴');
211        assert_eq!(BoxLines::Single.join(), '┼');
212
213        assert_eq!(BoxLines::Double.top_join(), '╦');
214        assert_eq!(BoxLines::Double.bottom_join(), '╩');
215        assert_eq!(BoxLines::Double.join(), '╬');
216
217        assert_eq!(BoxLines::Heavy.top_join(), '┳');
218        assert_eq!(BoxLines::Heavy.bottom_join(), '┻');
219        assert_eq!(BoxLines::Heavy.join(), '╋');
220
221        assert_eq!(BoxLines::Simple.top_join(), '+');
222        assert_eq!(BoxLines::Simple.bottom_join(), '+');
223        assert_eq!(BoxLines::Simple.join(), '+');
224
225        assert_eq!(BoxLines::Rounded.top_join(), '┳');
226        assert_eq!(BoxLines::Rounded.bottom_join(), '┻');
227        assert_eq!(BoxLines::Rounded.join(), '╋');
228
229        assert_eq!(BoxLines::Padded.top_join(), ' ');
230        assert_eq!(BoxLines::Padded.bottom_join(), ' ');
231        assert_eq!(BoxLines::Padded.join(), ' ');
232
233        assert_eq!(BoxLines::None.top_join(), ' ');
234        assert_eq!(BoxLines::None.bottom_join(), ' ');
235        assert_eq!(BoxLines::None.join(), ' ');
236    }
237
238    #[test]
239    fn outside_glyph_matches_tmux_border_tables() {
240        assert_eq!(BoxLines::Single.outside(), '·');
241        assert_eq!(BoxLines::Double.outside(), '·');
242        assert_eq!(BoxLines::Heavy.outside(), '·');
243        assert_eq!(BoxLines::Simple.outside(), '.');
244        assert_eq!(BoxLines::Rounded.outside(), '·');
245        assert_eq!(BoxLines::Padded.outside(), ' ');
246        assert_eq!(BoxLines::None.outside(), ' ');
247    }
248}