brewery

beer

public

A terminal worth pouring time into

Star0tarball
clone
ssh://git@git.notashelf.dev:33/notashelf/beer.git
crates/beer-protocols/src/sgr.rsRust98 lines3.0 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
54
/// `2[:cs]:r:g:b` (direct RGB, with an optional colour-space id that is
55
/// ignored).
56
fn color_from_subparams(sub: &[u16]) -> Option<Color> {
57
  match sub.first().copied() {
58
    Some(5) => sub.get(1).map(|&i| Color::Indexed(i as u8)),
59
    Some(2) => {
60
      // Either `2:r:g:b` or `2:colorspace:r:g:b`.
61
      let rgb = if sub.len() >= 5 {
62
        &sub[2..5]
63
      } else {
64
        &sub[1..]
65
      };
66
      match rgb {
67
        [r, g, b, ..] => Some(Color::Rgb(*r as u8, *g as u8, *b as u8)),
68
        _ => None,
69
      }
70
    },
71
    _ => None,
72
  }
73
}
74
75
#[cfg(test)]
76
mod tests {
77
  use super::*;
78
79
  #[test]
80
  fn underline_styles() {
81
    assert_eq!(underline_from(&[4]), Underline::Single);
82
    assert_eq!(underline_from(&[4, 3]), Underline::Curly);
83
    assert_eq!(underline_from(&[4, 0]), Underline::None);
84
  }
85
86
  #[test]
87
  fn extended_colour_semicolon_and_colon() {
88
    // `38;2;10;20;30` direct RGB.
89
    let items: Vec<&[u16]> = vec![&[38], &[2], &[10], &[20], &[30]];
90
    assert_eq!(ext_color(&items, 0), (Some(Color::Rgb(10, 20, 30)), 5));
91
    // `38:2:40:50:60` colon subparameters.
92
    let items: Vec<&[u16]> = vec![&[38, 2, 40, 50, 60]];
93
    assert_eq!(ext_color(&items, 0), (Some(Color::Rgb(40, 50, 60)), 1));
94
    // `38;5;1` indexed.
95
    let items: Vec<&[u16]> = vec![&[38], &[5], &[1]];
96
    assert_eq!(ext_color(&items, 0), (Some(Color::Indexed(1)), 3));
97
  }
98
}