brewery
notashelf /
a8e9e9cf7f9dc70c19ee8780bf3715b00d4ca09e

beer

public

A terminal worth pouring time into

Star0tarball
clone
ssh://git@git.notashelf.dev:33/notashelf/beer.git
crates/beer-protocols/src/sgr.rsRust97 lines3.3 KB
1
//! SGR (Select Graphic Rendition) parsing: the colour and underline forms that
2
//! need more than a single parameter. Plain attribute toggles (bold, reverse,
3
//! ...) stay in the dispatcher; what lives here is the multi-parameter parsing
4
//! that benefits from being testable in isolation.
5
6
use crate::style::{Color, Underline};
7
8
/// Map an SGR 4 parameter (`4` or `4:x`) to an underline style.
9
pub fn underline_from(param: &[u16]) -> Underline {
10
    match param.get(1).copied().unwrap_or(1) {
11
        0 => Underline::None,
12
        2 => Underline::Double,
13
        3 => Underline::Curly,
14
        4 => Underline::Dotted,
15
        5 => Underline::Dashed,
16
        _ => Underline::Single,
17
    }
18
}
19
20
/// Parse an SGR 38/48/58 extended colour, given the full parameter list and the
21
/// index of the introducer. Returns the colour and how many top-level
22
/// parameters it consumed (1 for the colon-subparameter form, more for the
23
/// legacy semicolon form).
24
pub fn ext_color(items: &[&[u16]], i: usize) -> (Option<Color>, usize) {
25
    let head = items[i];
26
    if head.len() >= 2 {
27
        return (color_from_subparams(&head[1..]), 1);
28
    }
29
    match items.get(i + 1).and_then(|s| s.first().copied()) {
30
        Some(5) => {
31
            let idx = items
32
                .get(i + 2)
33
                .and_then(|s| s.first().copied())
34
                .unwrap_or(0);
35
            (Some(Color::Indexed(idx as u8)), 3)
36
        }
37
        Some(2) => {
38
            let get = |k: usize| {
39
                items
40
                    .get(i + k)
41
                    .and_then(|s| s.first().copied())
42
                    .unwrap_or(0)
43
            };
44
            (
45
                Some(Color::Rgb(get(2) as u8, get(3) as u8, get(4) as u8)),
46
                5,
47
            )
48
        }
49
        _ => (None, 1),
50
    }
51
}
52
53
/// Parse the colon-subparameter colour form: `5:idx` (indexed) or `2[:cs]:r:g:b`
54
/// (direct RGB, with an optional colour-space id that is ignored).
55
fn color_from_subparams(sub: &[u16]) -> Option<Color> {
56
    match sub.first().copied() {
57
        Some(5) => sub.get(1).map(|&i| Color::Indexed(i as u8)),
58
        Some(2) => {
59
            // Either `2:r:g:b` or `2:colorspace:r:g:b`.
60
            let rgb = if sub.len() >= 5 {
61
                &sub[2..5]
62
            } else {
63
                &sub[1..]
64
            };
65
            match rgb {
66
                [r, g, b, ..] => Some(Color::Rgb(*r as u8, *g as u8, *b as u8)),
67
                _ => None,
68
            }
69
        }
70
        _ => None,
71
    }
72
}
73
74
#[cfg(test)]
75
mod tests {
76
    use super::*;
77
78
    #[test]
79
    fn underline_styles() {
80
        assert_eq!(underline_from(&[4]), Underline::Single);
81
        assert_eq!(underline_from(&[4, 3]), Underline::Curly);
82
        assert_eq!(underline_from(&[4, 0]), Underline::None);
83
    }
84
85
    #[test]
86
    fn extended_colour_semicolon_and_colon() {
87
        // `38;2;10;20;30` direct RGB.
88
        let items: Vec<&[u16]> = vec![&[38], &[2], &[10], &[20], &[30]];
89
        assert_eq!(ext_color(&items, 0), (Some(Color::Rgb(10, 20, 30)), 5));
90
        // `38:2:40:50:60` colon subparameters.
91
        let items: Vec<&[u16]> = vec![&[38, 2, 40, 50, 60]];
92
        assert_eq!(ext_color(&items, 0), (Some(Color::Rgb(40, 50, 60)), 1));
93
        // `38;5;1` indexed.
94
        let items: Vec<&[u16]> = vec![&[38], &[5], &[1]];
95
        assert_eq!(ext_color(&items, 0), (Some(Color::Indexed(1)), 3));
96
    }
97
}