Follow xparsecolor spec in escape sequences
Escape sequences in xterm are parsed according to xparsecolor. xparsecolor supports 1, 2, 3, and 4 digit hex colors. Previously, only 2 digits were supported. This also fixes a bug where "fX" was parsed as "0xf", where X is an invalid character. The response to a request for fg/bg must be a valid escape sequence. The current response uses 4-digit hex, which was previously invalid.
This commit is contained in:
parent
a8692983f5
commit
629ea247cd
|
@ -65,6 +65,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
and `debug.ref_test`
|
and `debug.ref_test`
|
||||||
- Select until next matching bracket when double-clicking a bracket
|
- Select until next matching bracket when double-clicking a bracket
|
||||||
- Added foreground/background escape code request sequences
|
- Added foreground/background escape code request sequences
|
||||||
|
- Escape sequences now support 1, 3, and 4 digit hex colors
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|
|
@ -24,63 +24,56 @@ use vte;
|
||||||
|
|
||||||
use crate::term::color::Rgb;
|
use crate::term::color::Rgb;
|
||||||
|
|
||||||
// Parse color arguments
|
// Parse colors in XParseColor format
|
||||||
//
|
fn xparse_color(color: &[u8]) -> Option<Rgb> {
|
||||||
// Expect that color argument looks like "rgb:xx/xx/xx" or "#xxxxxx"
|
if !color.is_empty() && color[0] == b'#' {
|
||||||
|
let len = color.len().saturating_sub(1);
|
||||||
|
parse_legacy_color(&color[1..len])
|
||||||
|
} else if color.len() >= 4 && &color[..4] == b"rgb:" {
|
||||||
|
let len = color.len().saturating_sub(1);
|
||||||
|
parse_rgb_color(&color[4..len])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse colors in `rgb:r(rrr)/g(ggg)/b(bbb)` format
|
||||||
fn parse_rgb_color(color: &[u8]) -> Option<Rgb> {
|
fn parse_rgb_color(color: &[u8]) -> Option<Rgb> {
|
||||||
let mut iter = color.iter();
|
let colors = str::from_utf8(color).ok()?.split('/').collect::<Vec<_>>();
|
||||||
|
|
||||||
macro_rules! next {
|
if colors.len() != 3 {
|
||||||
() => {
|
return None;
|
||||||
iter.next().map(|v| *v as char)
|
}
|
||||||
|
|
||||||
|
// Scale values instead of filling with `0`s
|
||||||
|
let scale = |input: &str| {
|
||||||
|
let max = u32::pow(16, input.len() as u32) - 1;
|
||||||
|
let value = u32::from_str_radix(input, 16).ok()?;
|
||||||
|
Some((255 * value / max) as u8)
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! parse_hex {
|
Some(Rgb {
|
||||||
() => {{
|
r: scale(colors[0])?,
|
||||||
let mut digit: u8 = 0;
|
g: scale(colors[1])?,
|
||||||
let next = next!().and_then(|v| v.to_digit(16));
|
b: scale(colors[2])?,
|
||||||
if let Some(value) = next {
|
})
|
||||||
digit = value as u8;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let next = next!().and_then(|v| v.to_digit(16));
|
// Parse colors in `#r(rrr)g(ggg)b(bbb)` format
|
||||||
if let Some(value) = next {
|
fn parse_legacy_color(color: &[u8]) -> Option<Rgb> {
|
||||||
digit <<= 4;
|
let item_len = color.len() / 3;
|
||||||
digit += value as u8;
|
|
||||||
}
|
|
||||||
digit
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
match next!() {
|
// Truncate/Fill to two byte precision
|
||||||
Some('r') => {
|
let color_from_slice = |slice: &[u8]| {
|
||||||
if next!() != Some('g') {
|
let col = usize::from_str_radix(str::from_utf8(slice).ok()?, 16).ok()? << 4;
|
||||||
return None;
|
Some((col >> (4 * slice.len().saturating_sub(1))) as u8)
|
||||||
}
|
};
|
||||||
if next!() != Some('b') {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
if next!() != Some(':') {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let r = parse_hex!();
|
Some(Rgb {
|
||||||
let val = next!();
|
r: color_from_slice(&color[0..item_len])?,
|
||||||
if val != Some('/') {
|
g: color_from_slice(&color[item_len..item_len * 2])?,
|
||||||
return None;
|
b: color_from_slice(&color[item_len * 2..])?,
|
||||||
}
|
})
|
||||||
let g = parse_hex!();
|
|
||||||
if next!() != Some('/') {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let b = parse_hex!();
|
|
||||||
|
|
||||||
Some(Rgb { r, g, b })
|
|
||||||
},
|
|
||||||
Some('#') => Some(Rgb { r: parse_hex!(), g: parse_hex!(), b: parse_hex!() }),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_number(input: &[u8]) -> Option<u8> {
|
fn parse_number(input: &[u8]) -> Option<u8> {
|
||||||
|
@ -781,7 +774,7 @@ where
|
||||||
if params.len() > 1 && params.len() % 2 != 0 {
|
if params.len() > 1 && params.len() % 2 != 0 {
|
||||||
for chunk in params[1..].chunks(2) {
|
for chunk in params[1..].chunks(2) {
|
||||||
let index = parse_number(chunk[0]);
|
let index = parse_number(chunk[0]);
|
||||||
let color = parse_rgb_color(chunk[1]);
|
let color = xparse_color(chunk[1]);
|
||||||
if let (Some(i), Some(c)) = (index, color) {
|
if let (Some(i), Some(c)) = (index, color) {
|
||||||
self.handler.set_color(i as usize, c);
|
self.handler.set_color(i as usize, c);
|
||||||
return;
|
return;
|
||||||
|
@ -806,7 +799,7 @@ where
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(color) = parse_rgb_color(param) {
|
if let Some(color) = xparse_color(param) {
|
||||||
self.handler.set_color(index, color);
|
self.handler.set_color(index, color);
|
||||||
} else if param == b"?" {
|
} else if param == b"?" {
|
||||||
self.handler.dynamic_color_sequence(writer, dynamic_code, index);
|
self.handler.dynamic_color_sequence(writer, dynamic_code, index);
|
||||||
|
@ -1203,7 +1196,7 @@ fn attrs_from_sgr_parameters(parameters: &[i64]) -> Vec<Option<Attr>> {
|
||||||
37 => Some(Attr::Foreground(Color::Named(NamedColor::White))),
|
37 => Some(Attr::Foreground(Color::Named(NamedColor::White))),
|
||||||
38 => {
|
38 => {
|
||||||
let mut start = 0;
|
let mut start = 0;
|
||||||
if let Some(color) = parse_color(¶meters[i..], &mut start) {
|
if let Some(color) = parse_sgr_color(¶meters[i..], &mut start) {
|
||||||
i += start;
|
i += start;
|
||||||
Some(Attr::Foreground(color))
|
Some(Attr::Foreground(color))
|
||||||
} else {
|
} else {
|
||||||
|
@ -1221,7 +1214,7 @@ fn attrs_from_sgr_parameters(parameters: &[i64]) -> Vec<Option<Attr>> {
|
||||||
47 => Some(Attr::Background(Color::Named(NamedColor::White))),
|
47 => Some(Attr::Background(Color::Named(NamedColor::White))),
|
||||||
48 => {
|
48 => {
|
||||||
let mut start = 0;
|
let mut start = 0;
|
||||||
if let Some(color) = parse_color(¶meters[i..], &mut start) {
|
if let Some(color) = parse_sgr_color(¶meters[i..], &mut start) {
|
||||||
i += start;
|
i += start;
|
||||||
Some(Attr::Background(color))
|
Some(Attr::Background(color))
|
||||||
} else {
|
} else {
|
||||||
|
@ -1256,7 +1249,7 @@ fn attrs_from_sgr_parameters(parameters: &[i64]) -> Vec<Option<Attr>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a color specifier from list of attributes
|
/// Parse a color specifier from list of attributes
|
||||||
fn parse_color(attrs: &[i64], i: &mut usize) -> Option<Color> {
|
fn parse_sgr_color(attrs: &[i64], i: &mut usize) -> Option<Color> {
|
||||||
if attrs.len() < 2 {
|
if attrs.len() < 2 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@ -1456,7 +1449,7 @@ pub mod C1 {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use super::{
|
||||||
parse_number, parse_rgb_color, Attr, CharsetIndex, Color, Handler, Processor,
|
parse_number, xparse_color, Attr, CharsetIndex, Color, Handler, Processor,
|
||||||
StandardCharset, TermInfo,
|
StandardCharset, TermInfo,
|
||||||
};
|
};
|
||||||
use crate::index::{Column, Line};
|
use crate::index::{Column, Line};
|
||||||
|
@ -1623,13 +1616,31 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_valid_rgb_color() {
|
fn parse_valid_rgb_colors() {
|
||||||
assert_eq!(parse_rgb_color(b"rgb:11/aa/ff"), Some(Rgb { r: 0x11, g: 0xaa, b: 0xff }));
|
assert_eq!(xparse_color(b"rgb:f/e/d\x07"), Some(Rgb { r: 0xff, g: 0xee, b: 0xdd }));
|
||||||
|
assert_eq!(xparse_color(b"rgb:11/aa/ff\x07"), Some(Rgb { r: 0x11, g: 0xaa, b: 0xff }));
|
||||||
|
assert_eq!(xparse_color(b"rgb:f/ed1/cb23\x07"), Some(Rgb { r: 0xff, g: 0xec, b: 0xca }));
|
||||||
|
assert_eq!(xparse_color(b"rgb:ffff/0/0\x07"), Some(Rgb { r: 0xff, g: 0x0, b: 0x0 }));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_valid_rgb_color2() {
|
fn parse_valid_legacy_rgb_colors() {
|
||||||
assert_eq!(parse_rgb_color(b"#11aaff"), Some(Rgb { r: 0x11, g: 0xaa, b: 0xff }));
|
assert_eq!(xparse_color(b"#1af\x07"), Some(Rgb { r: 0x10, g: 0xa0, b: 0xf0 }));
|
||||||
|
assert_eq!(xparse_color(b"#11aaff\x07"), Some(Rgb { r: 0x11, g: 0xaa, b: 0xff }));
|
||||||
|
assert_eq!(xparse_color(b"#110aa0ff0\x07"), Some(Rgb { r: 0x11, g: 0xaa, b: 0xff }));
|
||||||
|
assert_eq!(xparse_color(b"#1100aa00ff00\x07"), Some(Rgb { r: 0x11, g: 0xaa, b: 0xff }));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_invalid_rgb_colors() {
|
||||||
|
assert_eq!(xparse_color(b"rgb:0//\x07"), None);
|
||||||
|
assert_eq!(xparse_color(b"rgb://///\x07"), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_invalid_legacy_rgb_colors() {
|
||||||
|
assert_eq!(xparse_color(b"#\x07"), None);
|
||||||
|
assert_eq!(xparse_color(b"#f\x07"), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Reference in New Issue