1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-29 21:44:24 +03:00

vtparse: allow for CSI parameters to be : separated

This allows us to support the kitty style underline sequence,
or the : separated form of the true color escape sequences.

refs: https://github.com/wez/wezterm/issues/415
This commit is contained in:
Wez Furlong 2021-01-06 16:58:58 -08:00
parent eba263c8dd
commit b6a422a542
8 changed files with 496 additions and 310 deletions

2
Cargo.lock generated
View File

@ -4005,7 +4005,7 @@ dependencies = [
[[package]] [[package]]
name = "vtparse" name = "vtparse"
version = "0.3.0" version = "0.4.0"
dependencies = [ dependencies = [
"pretty_assertions", "pretty_assertions",
"utf8parse", "utf8parse",

View File

@ -30,7 +30,7 @@ terminfo = "0.7"
unicode-segmentation = "1.7" unicode-segmentation = "1.7"
unicode-width = "0.1" unicode-width = "0.1"
xi-unicode = "0.3" xi-unicode = "0.3"
vtparse = { version="0.3", path="../vtparse" } vtparse = { version="0.4", path="../vtparse" }
[features] [features]
widgets = ["cassowary", "fnv"] widgets = ["cassowary", "fnv"]

View File

@ -6,6 +6,8 @@ use num_derive::*;
use num_traits::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive};
use std::fmt::{Display, Error as FmtError, Formatter}; use std::fmt::{Display, Error as FmtError, Formatter};
pub use vtparse::CsiParam;
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum CSI { pub enum CSI {
/// SGR: Set Graphics Rendition. /// SGR: Set Graphics Rendition.
@ -33,15 +35,15 @@ pub enum CSI {
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct Unspecified { pub struct Unspecified {
params: Vec<i64>, pub params: Vec<CsiParam>,
// TODO: can we just make intermediates a single u8? // TODO: can we just make intermediates a single u8?
intermediates: Vec<u8>, pub intermediates: Vec<u8>,
/// if true, more than two intermediates arrived and the /// if true, more than two intermediates arrived and the
/// remaining data was ignored /// remaining data was ignored
ignored_extra_intermediates: bool, pub ignored_extra_intermediates: bool,
/// The final character in the CSI sequence; this typically /// The final character in the CSI sequence; this typically
/// defines how to interpret the other parameters. /// defines how to interpret the other parameters.
control: char, pub control: char,
} }
impl Display for Unspecified { impl Display for Unspecified {
@ -117,7 +119,7 @@ pub enum DeviceAttributeCodes {
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum DeviceAttribute { pub enum DeviceAttribute {
Code(DeviceAttributeCodes), Code(DeviceAttributeCodes),
Unspecified(u16), Unspecified(CsiParam),
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
@ -131,7 +133,7 @@ impl DeviceAttributeFlags {
for item in &self.attributes { for item in &self.attributes {
match item { match item {
DeviceAttribute::Code(c) => write!(f, ";{}", c.to_u16().ok_or_else(|| FmtError)?)?, DeviceAttribute::Code(c) => write!(f, ";{}", c.to_u16().ok_or_else(|| FmtError)?)?,
DeviceAttribute::Unspecified(c) => write!(f, ";{}", *c)?, DeviceAttribute::Unspecified(param) => write!(f, ";{}", param)?,
} }
} }
write!(f, "c")?; write!(f, "c")?;
@ -142,12 +144,15 @@ impl DeviceAttributeFlags {
Self { attributes } Self { attributes }
} }
fn from_params(params: &[i64]) -> Self { fn from_params(params: &[CsiParam]) -> Self {
let mut attributes = Vec::new(); let mut attributes = Vec::new();
for p in params { for i in params {
match FromPrimitive::from_i64(*p) { match i {
Some(c) => attributes.push(DeviceAttribute::Code(c)), CsiParam::Integer(p) => match FromPrimitive::from_i64(*p) {
None => attributes.push(DeviceAttribute::Unspecified(*p as u16)), Some(c) => attributes.push(DeviceAttribute::Code(c)),
None => attributes.push(DeviceAttribute::Unspecified(i.clone())),
},
_ => attributes.push(DeviceAttribute::Unspecified(i.clone())),
} }
} }
Self { attributes } Self { attributes }
@ -941,16 +946,16 @@ impl Display for Cursor {
/// but in some we build out an enum. The trait helps to generalize /// but in some we build out an enum. The trait helps to generalize
/// the parser code while keeping it relatively terse. /// the parser code while keeping it relatively terse.
trait ParseParams: Sized { trait ParseParams: Sized {
fn parse_params(params: &[i64]) -> Result<Self, ()>; fn parse_params(params: &[CsiParam]) -> Result<Self, ()>;
} }
/// Parse an input parameter into a 1-based unsigned value /// Parse an input parameter into a 1-based unsigned value
impl ParseParams for u32 { impl ParseParams for u32 {
fn parse_params(params: &[i64]) -> Result<u32, ()> { fn parse_params(params: &[CsiParam]) -> Result<u32, ()> {
if params.is_empty() { if params.is_empty() {
Ok(1) Ok(1)
} else if params.len() == 1 { } else if params.len() == 1 {
to_1b_u32(params[0]) to_1b_u32(&params[0])
} else { } else {
Err(()) Err(())
} }
@ -959,11 +964,11 @@ impl ParseParams for u32 {
/// Parse an input parameter into a 1-based unsigned value /// Parse an input parameter into a 1-based unsigned value
impl ParseParams for OneBased { impl ParseParams for OneBased {
fn parse_params(params: &[i64]) -> Result<OneBased, ()> { fn parse_params(params: &[CsiParam]) -> Result<OneBased, ()> {
if params.is_empty() { if params.is_empty() {
Ok(OneBased::new(1)) Ok(OneBased::new(1))
} else if params.len() == 1 { } else if params.len() == 1 {
OneBased::from_esc_param(params[0]) OneBased::from_esc_param(&params[0])
} else { } else {
Err(()) Err(())
} }
@ -974,15 +979,15 @@ impl ParseParams for OneBased {
/// This is typically used to build a struct comprised of /// This is typically used to build a struct comprised of
/// the pair of values. /// the pair of values.
impl ParseParams for (OneBased, OneBased) { impl ParseParams for (OneBased, OneBased) {
fn parse_params(params: &[i64]) -> Result<(OneBased, OneBased), ()> { fn parse_params(params: &[CsiParam]) -> Result<(OneBased, OneBased), ()> {
if params.is_empty() { if params.is_empty() {
Ok((OneBased::new(1), OneBased::new(1))) Ok((OneBased::new(1), OneBased::new(1)))
} else if params.len() == 1 { } else if params.len() == 1 {
Ok((OneBased::from_esc_param(params[0])?, OneBased::new(1))) Ok((OneBased::from_esc_param(&params[0])?, OneBased::new(1)))
} else if params.len() == 2 { } else if params.len() == 2 {
Ok(( Ok((
OneBased::from_esc_param(params[0])?, OneBased::from_esc_param(&params[0])?,
OneBased::from_esc_param(params[1])?, OneBased::from_esc_param(&params[1])?,
)) ))
} else { } else {
Err(()) Err(())
@ -1000,11 +1005,14 @@ trait ParamEnum: FromPrimitive {
/// implement ParseParams for the enums that also implement ParamEnum. /// implement ParseParams for the enums that also implement ParamEnum.
impl<T: ParamEnum> ParseParams for T { impl<T: ParamEnum> ParseParams for T {
fn parse_params(params: &[i64]) -> Result<Self, ()> { fn parse_params(params: &[CsiParam]) -> Result<Self, ()> {
if params.is_empty() { if params.is_empty() {
Ok(ParamEnum::default()) Ok(ParamEnum::default())
} else if params.len() == 1 { } else if params.len() == 1 {
FromPrimitive::from_i64(params[0]).ok_or(()) match params[0] {
CsiParam::Integer(i) => FromPrimitive::from_i64(i).ok_or(()),
CsiParam::ColonList(_) => Err(()),
}
} else { } else {
Err(()) Err(())
} }
@ -1257,7 +1265,7 @@ struct CSIParser<'a> {
/// In a number of cases an empty params list is used to indicate /// In a number of cases an empty params list is used to indicate
/// default values, especially for SGR, so we need to be careful not /// default values, especially for SGR, so we need to be careful not
/// to update params to an empty slice. /// to update params to an empty slice.
params: Option<&'a [i64]>, params: Option<&'a [CsiParam]>,
} }
impl CSI { impl CSI {
@ -1268,7 +1276,7 @@ impl CSI {
/// If no semantic meaning is known for a subsequence, the remainder /// If no semantic meaning is known for a subsequence, the remainder
/// of the sequence is returned wrapped in a `CSI::Unspecified` container. /// of the sequence is returned wrapped in a `CSI::Unspecified` container.
pub fn parse<'a>( pub fn parse<'a>(
params: &'a [i64], params: &'a [CsiParam],
intermediates: &'a [u8], intermediates: &'a [u8],
ignored_extra_intermediates: bool, ignored_extra_intermediates: bool,
control: char, control: char,
@ -1283,11 +1291,16 @@ impl CSI {
} }
/// A little helper to convert i64 -> u8 if safe /// A little helper to convert i64 -> u8 if safe
fn to_u8(v: i64) -> Result<u8, ()> { fn to_u8(v: &CsiParam) -> Result<u8, ()> {
if v <= i64::from(u8::max_value()) { match v {
Ok(v as u8) CsiParam::ColonList(_) => Err(()),
} else { CsiParam::Integer(v) => {
Err(()) if *v <= i64::from(u8::max_value()) {
Ok(*v as u8)
} else {
Err(())
}
}
} }
} }
@ -1302,13 +1315,11 @@ fn to_u8(v: i64) -> Result<u8, ()> {
/// otherwise outside that range, an error is propagated and /// otherwise outside that range, an error is propagated and
/// that will typically case the sequence to be reported via /// that will typically case the sequence to be reported via
/// the Unspecified placeholder. /// the Unspecified placeholder.
fn to_1b_u32(v: i64) -> Result<u32, ()> { fn to_1b_u32(v: &CsiParam) -> Result<u32, ()> {
if v == 0 { match v {
Ok(1) CsiParam::Integer(v) if *v == 0 => Ok(1),
} else if v > 0 && v <= i64::from(u32::max_value()) { CsiParam::Integer(v) if *v > 0 && *v <= i64::from(u32::max_value()) => Ok(*v as u32),
Ok(v as u32) _ => Err(()),
} else {
Err(())
} }
} }
@ -1338,7 +1349,7 @@ macro_rules! parse {
} }
impl<'a> CSIParser<'a> { impl<'a> CSIParser<'a> {
fn parse_next(&mut self, params: &'a [i64]) -> Result<CSI, ()> { fn parse_next(&mut self, params: &'a [CsiParam]) -> Result<CSI, ()> {
match (self.control, self.intermediates) { match (self.control, self.intermediates) {
('@', &[]) => parse!(Edit, InsertCharacter, params), ('@', &[]) => parse!(Edit, InsertCharacter, params),
('`', &[]) => parse!(Cursor, CharacterPositionAbsolute, params), ('`', &[]) => parse!(Cursor, CharacterPositionAbsolute, params),
@ -1387,8 +1398,8 @@ impl<'a> CSIParser<'a> {
('t', &[]) => self.window(params).map(CSI::Window), ('t', &[]) => self.window(params).map(CSI::Window),
('u', &[]) => noparams!(Cursor, RestoreCursor, params), ('u', &[]) => noparams!(Cursor, RestoreCursor, params),
('y', &[b'*']) => { ('y', &[b'*']) => {
fn p(params: &[i64], idx: usize) -> Result<i64, ()> { fn p(params: &[CsiParam], idx: usize) -> Result<i64, ()> {
params.get(idx).cloned().ok_or(()) params.get(idx).and_then(CsiParam::as_integer).ok_or(())
} }
let request_id = p(params, 0)?; let request_id = p(params, 0)?;
let page_number = p(params, 1)?; let page_number = p(params, 1)?;
@ -1445,7 +1456,7 @@ impl<'a> CSIParser<'a> {
/// Take care to avoid setting params back to an empty slice /// Take care to avoid setting params back to an empty slice
/// as this would trigger returning a default value and/or /// as this would trigger returning a default value and/or
/// an unterminated parse loop. /// an unterminated parse loop.
fn advance_by<T>(&mut self, n: usize, params: &'a [i64], result: T) -> T { fn advance_by<T>(&mut self, n: usize, params: &'a [CsiParam], result: T) -> T {
let (_, next) = params.split_at(n); let (_, next) = params.split_at(n);
if !next.is_empty() { if !next.is_empty() {
self.params = Some(next); self.params = Some(next);
@ -1453,11 +1464,11 @@ impl<'a> CSIParser<'a> {
result result
} }
fn cursor_style(&mut self, params: &'a [i64]) -> Result<CSI, ()> { fn cursor_style(&mut self, params: &'a [CsiParam]) -> Result<CSI, ()> {
if params.len() != 1 { if params.len() != 1 {
Err(()) Err(())
} else { } else {
match FromPrimitive::from_i64(params[0]) { match FromPrimitive::from_i64(params[0].as_integer().unwrap()) {
None => Err(()), None => Err(()),
Some(style) => { Some(style) => {
Ok(self.advance_by(1, params, CSI::Cursor(Cursor::CursorStyle(style)))) Ok(self.advance_by(1, params, CSI::Cursor(Cursor::CursorStyle(style))))
@ -1466,17 +1477,17 @@ impl<'a> CSIParser<'a> {
} }
} }
fn dsr(&mut self, params: &'a [i64]) -> Result<CSI, ()> { fn dsr(&mut self, params: &'a [CsiParam]) -> Result<CSI, ()> {
if params == [5] { if params == [CsiParam::Integer(5)] {
Ok(self.advance_by(1, params, CSI::Device(Box::new(Device::StatusReport)))) Ok(self.advance_by(1, params, CSI::Device(Box::new(Device::StatusReport))))
} else if params == [6] { } else if params == [CsiParam::Integer(6)] {
Ok(self.advance_by(1, params, CSI::Cursor(Cursor::RequestActivePositionReport))) Ok(self.advance_by(1, params, CSI::Cursor(Cursor::RequestActivePositionReport)))
} else { } else {
Err(()) Err(())
} }
} }
fn decstbm(&mut self, params: &'a [i64]) -> Result<CSI, ()> { fn decstbm(&mut self, params: &'a [CsiParam]) -> Result<CSI, ()> {
if params.is_empty() { if params.is_empty() {
Ok(CSI::Cursor(Cursor::SetTopAndBottomMargins { Ok(CSI::Cursor(Cursor::SetTopAndBottomMargins {
top: OneBased::new(1), top: OneBased::new(1),
@ -1487,7 +1498,7 @@ impl<'a> CSIParser<'a> {
1, 1,
params, params,
CSI::Cursor(Cursor::SetTopAndBottomMargins { CSI::Cursor(Cursor::SetTopAndBottomMargins {
top: OneBased::from_esc_param(params[0])?, top: OneBased::from_esc_param(&params[0])?,
bottom: OneBased::new(u32::max_value()), bottom: OneBased::new(u32::max_value()),
}), }),
)) ))
@ -1496,8 +1507,8 @@ impl<'a> CSIParser<'a> {
2, 2,
params, params,
CSI::Cursor(Cursor::SetTopAndBottomMargins { CSI::Cursor(Cursor::SetTopAndBottomMargins {
top: OneBased::from_esc_param(params[0])?, top: OneBased::from_esc_param(&params[0])?,
bottom: OneBased::from_esc_param_with_big_default(params[1])?, bottom: OneBased::from_esc_param_with_big_default(&params[1])?,
}), }),
)) ))
} else { } else {
@ -1505,19 +1516,21 @@ impl<'a> CSIParser<'a> {
} }
} }
fn xterm_key_modifier(&mut self, params: &'a [i64]) -> Result<CSI, ()> { fn xterm_key_modifier(&mut self, params: &'a [CsiParam]) -> Result<CSI, ()> {
if params.len() == 2 { if params.len() == 2 {
let resource = XtermKeyModifierResource::parse(params[0]).ok_or_else(|| ())?; let resource = XtermKeyModifierResource::parse(params[0].as_integer().unwrap())
.ok_or_else(|| ())?;
Ok(self.advance_by( Ok(self.advance_by(
2, 2,
params, params,
CSI::Mode(Mode::XtermKeyMode { CSI::Mode(Mode::XtermKeyMode {
resource, resource,
value: Some(params[1]), value: Some(params[1].as_integer().ok_or_else(|| ())?),
}), }),
)) ))
} else if params.len() == 1 { } else if params.len() == 1 {
let resource = XtermKeyModifierResource::parse(params[0]).ok_or_else(|| ())?; let resource = XtermKeyModifierResource::parse(params[0].as_integer().unwrap())
.ok_or_else(|| ())?;
Ok(self.advance_by( Ok(self.advance_by(
1, 1,
params, params,
@ -1531,7 +1544,7 @@ impl<'a> CSIParser<'a> {
} }
} }
fn decslrm(&mut self, params: &'a [i64]) -> Result<CSI, ()> { fn decslrm(&mut self, params: &'a [CsiParam]) -> Result<CSI, ()> {
if params.is_empty() { if params.is_empty() {
// with no params this is a request to save the cursor // with no params this is a request to save the cursor
// and is technically in conflict with SetLeftAndRightMargins. // and is technically in conflict with SetLeftAndRightMargins.
@ -1544,7 +1557,7 @@ impl<'a> CSIParser<'a> {
1, 1,
params, params,
CSI::Cursor(Cursor::SetLeftAndRightMargins { CSI::Cursor(Cursor::SetLeftAndRightMargins {
left: OneBased::from_esc_param(params[0])?, left: OneBased::from_esc_param(&params[0])?,
right: OneBased::new(u32::max_value()), right: OneBased::new(u32::max_value()),
}), }),
)) ))
@ -1553,8 +1566,8 @@ impl<'a> CSIParser<'a> {
2, 2,
params, params,
CSI::Cursor(Cursor::SetLeftAndRightMargins { CSI::Cursor(Cursor::SetLeftAndRightMargins {
left: OneBased::from_esc_param(params[0])?, left: OneBased::from_esc_param(&params[0])?,
right: OneBased::from_esc_param(params[1])?, right: OneBased::from_esc_param(&params[1])?,
}), }),
)) ))
} else { } else {
@ -1562,52 +1575,52 @@ impl<'a> CSIParser<'a> {
} }
} }
fn req_primary_device_attributes(&mut self, params: &'a [i64]) -> Result<Device, ()> { fn req_primary_device_attributes(&mut self, params: &'a [CsiParam]) -> Result<Device, ()> {
if params == [] { if params == [] {
Ok(Device::RequestPrimaryDeviceAttributes) Ok(Device::RequestPrimaryDeviceAttributes)
} else if params == [0] { } else if params == [CsiParam::Integer(0)] {
Ok(self.advance_by(1, params, Device::RequestPrimaryDeviceAttributes)) Ok(self.advance_by(1, params, Device::RequestPrimaryDeviceAttributes))
} else { } else {
Err(()) Err(())
} }
} }
fn req_terminal_name_and_version(&mut self, params: &'a [i64]) -> Result<Device, ()> { fn req_terminal_name_and_version(&mut self, params: &'a [CsiParam]) -> Result<Device, ()> {
if params == [] { if params == [] {
Ok(Device::RequestTerminalNameAndVersion) Ok(Device::RequestTerminalNameAndVersion)
} else if params == [0] { } else if params == [CsiParam::Integer(0)] {
Ok(self.advance_by(1, params, Device::RequestTerminalNameAndVersion)) Ok(self.advance_by(1, params, Device::RequestTerminalNameAndVersion))
} else { } else {
Err(()) Err(())
} }
} }
fn req_secondary_device_attributes(&mut self, params: &'a [i64]) -> Result<Device, ()> { fn req_secondary_device_attributes(&mut self, params: &'a [CsiParam]) -> Result<Device, ()> {
if params == [] { if params == [] {
Ok(Device::RequestSecondaryDeviceAttributes) Ok(Device::RequestSecondaryDeviceAttributes)
} else if params == [0] { } else if params == [CsiParam::Integer(0)] {
Ok(self.advance_by(1, params, Device::RequestSecondaryDeviceAttributes)) Ok(self.advance_by(1, params, Device::RequestSecondaryDeviceAttributes))
} else { } else {
Err(()) Err(())
} }
} }
fn secondary_device_attributes(&mut self, params: &'a [i64]) -> Result<Device, ()> { fn secondary_device_attributes(&mut self, params: &'a [CsiParam]) -> Result<Device, ()> {
if params == [1, 0] { if params == [CsiParam::Integer(1), CsiParam::Integer(0)] {
Ok(self.advance_by( Ok(self.advance_by(
2, 2,
params, params,
Device::DeviceAttributes(DeviceAttributes::Vt101WithNoOptions), Device::DeviceAttributes(DeviceAttributes::Vt101WithNoOptions),
)) ))
} else if params == [6] { } else if params == [CsiParam::Integer(6)] {
Ok(self.advance_by(1, params, Device::DeviceAttributes(DeviceAttributes::Vt102))) Ok(self.advance_by(1, params, Device::DeviceAttributes(DeviceAttributes::Vt102)))
} else if params == [1, 2] { } else if params == [CsiParam::Integer(1), CsiParam::Integer(2)] {
Ok(self.advance_by( Ok(self.advance_by(
2, 2,
params, params,
Device::DeviceAttributes(DeviceAttributes::Vt100WithAdvancedVideoOption), Device::DeviceAttributes(DeviceAttributes::Vt100WithAdvancedVideoOption),
)) ))
} else if !params.is_empty() && params[0] == 62 { } else if !params.is_empty() && params[0] == CsiParam::Integer(62) {
Ok(self.advance_by( Ok(self.advance_by(
params.len(), params.len(),
params, params,
@ -1615,7 +1628,7 @@ impl<'a> CSIParser<'a> {
DeviceAttributeFlags::from_params(&params[1..]), DeviceAttributeFlags::from_params(&params[1..]),
)), )),
)) ))
} else if !params.is_empty() && params[0] == 63 { } else if !params.is_empty() && params[0] == CsiParam::Integer(63) {
Ok(self.advance_by( Ok(self.advance_by(
params.len(), params.len(),
params, params,
@ -1623,7 +1636,7 @@ impl<'a> CSIParser<'a> {
DeviceAttributeFlags::from_params(&params[1..]), DeviceAttributeFlags::from_params(&params[1..]),
)), )),
)) ))
} else if !params.is_empty() && params[0] == 64 { } else if !params.is_empty() && params[0] == CsiParam::Integer(64) {
Ok(self.advance_by( Ok(self.advance_by(
params.len(), params.len(),
params, params,
@ -1637,13 +1650,15 @@ impl<'a> CSIParser<'a> {
} }
/// Parse extended mouse reports known as SGR 1006 mode /// Parse extended mouse reports known as SGR 1006 mode
fn mouse_sgr1006(&mut self, params: &'a [i64]) -> Result<MouseReport, ()> { fn mouse_sgr1006(&mut self, params: &'a [CsiParam]) -> Result<MouseReport, ()> {
if params.len() != 3 { if params.len() != 3 {
return Err(()); return Err(());
} }
let p0 = params[0].as_integer().unwrap();
// 'M' encodes a press, 'm' a release. // 'M' encodes a press, 'm' a release.
let button = match (self.control, params[0] & 0b110_0011) { let button = match (self.control, p0 & 0b110_0011) {
('M', 0) => MouseButton::Button1Press, ('M', 0) => MouseButton::Button1Press,
('m', 0) => MouseButton::Button1Release, ('m', 0) => MouseButton::Button1Release,
('M', 1) => MouseButton::Button2Press, ('M', 1) => MouseButton::Button2Press,
@ -1672,30 +1687,36 @@ impl<'a> CSIParser<'a> {
}; };
let mut modifiers = Modifiers::NONE; let mut modifiers = Modifiers::NONE;
if params[0] & 4 != 0 { if p0 & 4 != 0 {
modifiers |= Modifiers::SHIFT; modifiers |= Modifiers::SHIFT;
} }
if params[0] & 8 != 0 { if p0 & 8 != 0 {
modifiers |= Modifiers::ALT; modifiers |= Modifiers::ALT;
} }
if params[0] & 16 != 0 { if p0 & 16 != 0 {
modifiers |= Modifiers::CTRL; modifiers |= Modifiers::CTRL;
} }
let p1 = params[1].as_integer().unwrap();
let p2 = params[2].as_integer().unwrap();
Ok(self.advance_by( Ok(self.advance_by(
3, 3,
params, params,
MouseReport::SGR1006 { MouseReport::SGR1006 {
x: params[1] as u16, x: p1 as u16,
y: params[2] as u16, y: p2 as u16,
button, button,
modifiers, modifiers,
}, },
)) ))
} }
fn dec(&mut self, params: &'a [i64]) -> Result<DecPrivateMode, ()> { fn dec(&mut self, params: &'a [CsiParam]) -> Result<DecPrivateMode, ()> {
let p0 = *params.get(0).ok_or_else(|| ())?; let p0 = params
.get(0)
.and_then(CsiParam::as_integer)
.ok_or_else(|| ())?;
match FromPrimitive::from_i64(p0) { match FromPrimitive::from_i64(p0) {
None => Ok(self.advance_by( None => Ok(self.advance_by(
1, 1,
@ -1706,8 +1727,11 @@ impl<'a> CSIParser<'a> {
} }
} }
fn terminal_mode(&mut self, params: &'a [i64]) -> Result<TerminalMode, ()> { fn terminal_mode(&mut self, params: &'a [CsiParam]) -> Result<TerminalMode, ()> {
let p0 = *params.get(0).ok_or_else(|| ())?; let p0 = params
.get(0)
.and_then(CsiParam::as_integer)
.ok_or_else(|| ())?;
match FromPrimitive::from_i64(p0) { match FromPrimitive::from_i64(p0) {
None => { None => {
Ok(self.advance_by(1, params, TerminalMode::Unspecified(p0.to_u16().ok_or(())?))) Ok(self.advance_by(1, params, TerminalMode::Unspecified(p0.to_u16().ok_or(())?)))
@ -1716,100 +1740,103 @@ impl<'a> CSIParser<'a> {
} }
} }
fn parse_sgr_color(&mut self, params: &'a [i64]) -> Result<ColorSpec, ()> { fn parse_sgr_color(&mut self, params: &'a [CsiParam]) -> Result<ColorSpec, ()> {
if params.len() >= 5 && params[1] == 2 { if params.len() >= 5 && params[1].as_integer() == Some(2) {
let red = to_u8(params[2])?; let red = to_u8(&params[2])?;
let green = to_u8(params[3])?; let green = to_u8(&params[3])?;
let blue = to_u8(params[4])?; let blue = to_u8(&params[4])?;
let res = RgbColor::new(red, green, blue).into(); let res = RgbColor::new(red, green, blue).into();
Ok(self.advance_by(5, params, res)) Ok(self.advance_by(5, params, res))
} else if params.len() >= 3 && params[1] == 5 { } else if params.len() >= 3 && params[1].as_integer() == Some(5) {
let idx = to_u8(params[2])?; let idx = to_u8(&params[2])?;
Ok(self.advance_by(3, params, ColorSpec::PaletteIndex(idx))) Ok(self.advance_by(3, params, ColorSpec::PaletteIndex(idx)))
} else { } else {
Err(()) Err(())
} }
} }
fn window(&mut self, params: &'a [i64]) -> Result<Window, ()> { fn window(&mut self, params: &'a [CsiParam]) -> Result<Window, ()> {
if params.is_empty() { if params.is_empty() {
Err(()) Err(())
} else { } else {
let arg1 = params.get(1).cloned(); let arg1 = params.get(1).and_then(CsiParam::as_integer);
let arg2 = params.get(2).cloned(); let arg2 = params.get(2).and_then(CsiParam::as_integer);
match params[0] { match params[0].as_integer() {
1 => Ok(Window::DeIconify), None => Err(()),
2 => Ok(Window::Iconify), Some(p) => match p {
3 => Ok(Window::MoveWindow { 1 => Ok(Window::DeIconify),
x: arg1.unwrap_or(0), 2 => Ok(Window::Iconify),
y: arg2.unwrap_or(0), 3 => Ok(Window::MoveWindow {
}), x: arg1.unwrap_or(0),
4 => Ok(Window::ResizeWindowPixels { y: arg2.unwrap_or(0),
height: arg1, }),
width: arg2, 4 => Ok(Window::ResizeWindowPixels {
}),
5 => Ok(Window::RaiseWindow),
6 => match params.len() {
1 => Ok(Window::LowerWindow),
3 => Ok(Window::ReportCellSizePixelsResponse {
height: arg1, height: arg1,
width: arg2, width: arg2,
}), }),
5 => Ok(Window::RaiseWindow),
6 => match params.len() {
1 => Ok(Window::LowerWindow),
3 => Ok(Window::ReportCellSizePixelsResponse {
height: arg1,
width: arg2,
}),
_ => Err(()),
},
7 => Ok(Window::RefreshWindow),
8 => Ok(Window::ResizeWindowCells {
height: arg1,
width: arg2,
}),
9 => match arg1 {
Some(0) => Ok(Window::RestoreMaximizedWindow),
Some(1) => Ok(Window::MaximizeWindow),
Some(2) => Ok(Window::MaximizeWindowVertically),
Some(3) => Ok(Window::MaximizeWindowHorizontally),
_ => Err(()),
},
10 => match arg1 {
Some(0) => Ok(Window::UndoFullScreenMode),
Some(1) => Ok(Window::ChangeToFullScreenMode),
Some(2) => Ok(Window::ToggleFullScreen),
_ => Err(()),
},
11 => Ok(Window::ReportWindowState),
13 => match arg1 {
None => Ok(Window::ReportWindowPosition),
Some(2) => Ok(Window::ReportTextAreaPosition),
_ => Err(()),
},
14 => match arg1 {
None => Ok(Window::ReportTextAreaSizePixels),
Some(2) => Ok(Window::ReportWindowSizePixels),
_ => Err(()),
},
15 => Ok(Window::ReportScreenSizePixels),
16 => Ok(Window::ReportCellSizePixels),
18 => Ok(Window::ReportTextAreaSizeCells),
19 => Ok(Window::ReportScreenSizeCells),
20 => Ok(Window::ReportIconLabel),
21 => Ok(Window::ReportWindowTitle),
22 => match arg1 {
Some(0) => Ok(Window::PushIconAndWindowTitle),
Some(1) => Ok(Window::PushIconTitle),
Some(2) => Ok(Window::PushWindowTitle),
_ => Err(()),
},
23 => match arg1 {
Some(0) => Ok(Window::PopIconAndWindowTitle),
Some(1) => Ok(Window::PopIconTitle),
Some(2) => Ok(Window::PopWindowTitle),
_ => Err(()),
},
_ => Err(()), _ => Err(()),
}, },
7 => Ok(Window::RefreshWindow),
8 => Ok(Window::ResizeWindowCells {
height: arg1,
width: arg2,
}),
9 => match arg1 {
Some(0) => Ok(Window::RestoreMaximizedWindow),
Some(1) => Ok(Window::MaximizeWindow),
Some(2) => Ok(Window::MaximizeWindowVertically),
Some(3) => Ok(Window::MaximizeWindowHorizontally),
_ => Err(()),
},
10 => match arg1 {
Some(0) => Ok(Window::UndoFullScreenMode),
Some(1) => Ok(Window::ChangeToFullScreenMode),
Some(2) => Ok(Window::ToggleFullScreen),
_ => Err(()),
},
11 => Ok(Window::ReportWindowState),
13 => match arg1 {
None => Ok(Window::ReportWindowPosition),
Some(2) => Ok(Window::ReportTextAreaPosition),
_ => Err(()),
},
14 => match arg1 {
None => Ok(Window::ReportTextAreaSizePixels),
Some(2) => Ok(Window::ReportWindowSizePixels),
_ => Err(()),
},
15 => Ok(Window::ReportScreenSizePixels),
16 => Ok(Window::ReportCellSizePixels),
18 => Ok(Window::ReportTextAreaSizeCells),
19 => Ok(Window::ReportScreenSizeCells),
20 => Ok(Window::ReportIconLabel),
21 => Ok(Window::ReportWindowTitle),
22 => match arg1 {
Some(0) => Ok(Window::PushIconAndWindowTitle),
Some(1) => Ok(Window::PushIconTitle),
Some(2) => Ok(Window::PushWindowTitle),
_ => Err(()),
},
23 => match arg1 {
Some(0) => Ok(Window::PopIconAndWindowTitle),
Some(1) => Ok(Window::PopIconTitle),
Some(2) => Ok(Window::PopWindowTitle),
_ => Err(()),
},
_ => Err(()),
} }
} }
} }
fn sgr(&mut self, params: &'a [i64]) -> Result<Sgr, ()> { fn sgr(&mut self, params: &'a [CsiParam]) -> Result<Sgr, ()> {
if params.is_empty() { if params.is_empty() {
// With no parameters, treat as equivalent to Reset. // With no parameters, treat as equivalent to Reset.
Ok(Sgr::Reset) Ok(Sgr::Reset)
@ -1821,97 +1848,130 @@ impl<'a> CSIParser<'a> {
}; };
}; };
match FromPrimitive::from_i64(params[0]) { match params[0] {
None => Err(()), CsiParam::Integer(i) => match FromPrimitive::from_i64(i) {
Some(sgr) => match sgr { None => Err(()),
SgrCode::Reset => one!(Sgr::Reset), Some(sgr) => match sgr {
SgrCode::IntensityBold => one!(Sgr::Intensity(Intensity::Bold)), SgrCode::Reset => one!(Sgr::Reset),
SgrCode::IntensityDim => one!(Sgr::Intensity(Intensity::Half)), SgrCode::IntensityBold => one!(Sgr::Intensity(Intensity::Bold)),
SgrCode::NormalIntensity => one!(Sgr::Intensity(Intensity::Normal)), SgrCode::IntensityDim => one!(Sgr::Intensity(Intensity::Half)),
SgrCode::UnderlineOn => one!(Sgr::Underline(Underline::Single)), SgrCode::NormalIntensity => one!(Sgr::Intensity(Intensity::Normal)),
SgrCode::UnderlineDouble => one!(Sgr::Underline(Underline::Double)), SgrCode::UnderlineOn => one!(Sgr::Underline(Underline::Single)),
SgrCode::UnderlineCurly => one!(Sgr::Underline(Underline::Curly)), SgrCode::UnderlineDouble => one!(Sgr::Underline(Underline::Double)),
SgrCode::UnderlineDotted => one!(Sgr::Underline(Underline::Dotted)), SgrCode::UnderlineCurly => one!(Sgr::Underline(Underline::Curly)),
SgrCode::UnderlineDashed => one!(Sgr::Underline(Underline::Dashed)), SgrCode::UnderlineDotted => one!(Sgr::Underline(Underline::Dotted)),
SgrCode::UnderlineOff => one!(Sgr::Underline(Underline::None)), SgrCode::UnderlineDashed => one!(Sgr::Underline(Underline::Dashed)),
SgrCode::UnderlineColor => { SgrCode::UnderlineOff => one!(Sgr::Underline(Underline::None)),
self.parse_sgr_color(params).map(Sgr::UnderlineColor) SgrCode::UnderlineColor => {
} self.parse_sgr_color(params).map(Sgr::UnderlineColor)
SgrCode::ResetUnderlineColor => one!(Sgr::UnderlineColor(ColorSpec::default())), }
SgrCode::BlinkOn => one!(Sgr::Blink(Blink::Slow)), SgrCode::ResetUnderlineColor => {
SgrCode::RapidBlinkOn => one!(Sgr::Blink(Blink::Rapid)), one!(Sgr::UnderlineColor(ColorSpec::default()))
SgrCode::BlinkOff => one!(Sgr::Blink(Blink::None)), }
SgrCode::ItalicOn => one!(Sgr::Italic(true)), SgrCode::BlinkOn => one!(Sgr::Blink(Blink::Slow)),
SgrCode::ItalicOff => one!(Sgr::Italic(false)), SgrCode::RapidBlinkOn => one!(Sgr::Blink(Blink::Rapid)),
SgrCode::ForegroundColor => self.parse_sgr_color(params).map(Sgr::Foreground), SgrCode::BlinkOff => one!(Sgr::Blink(Blink::None)),
SgrCode::ForegroundBlack => one!(Sgr::Foreground(AnsiColor::Black.into())), SgrCode::ItalicOn => one!(Sgr::Italic(true)),
SgrCode::ForegroundRed => one!(Sgr::Foreground(AnsiColor::Maroon.into())), SgrCode::ItalicOff => one!(Sgr::Italic(false)),
SgrCode::ForegroundGreen => one!(Sgr::Foreground(AnsiColor::Green.into())), SgrCode::ForegroundColor => {
SgrCode::ForegroundYellow => one!(Sgr::Foreground(AnsiColor::Olive.into())), self.parse_sgr_color(params).map(Sgr::Foreground)
SgrCode::ForegroundBlue => one!(Sgr::Foreground(AnsiColor::Navy.into())), }
SgrCode::ForegroundMagenta => one!(Sgr::Foreground(AnsiColor::Purple.into())), SgrCode::ForegroundBlack => one!(Sgr::Foreground(AnsiColor::Black.into())),
SgrCode::ForegroundCyan => one!(Sgr::Foreground(AnsiColor::Teal.into())), SgrCode::ForegroundRed => one!(Sgr::Foreground(AnsiColor::Maroon.into())),
SgrCode::ForegroundWhite => one!(Sgr::Foreground(AnsiColor::Silver.into())), SgrCode::ForegroundGreen => one!(Sgr::Foreground(AnsiColor::Green.into())),
SgrCode::ForegroundDefault => one!(Sgr::Foreground(ColorSpec::Default)), SgrCode::ForegroundYellow => one!(Sgr::Foreground(AnsiColor::Olive.into())),
SgrCode::ForegroundBrightBlack => one!(Sgr::Foreground(AnsiColor::Grey.into())), SgrCode::ForegroundBlue => one!(Sgr::Foreground(AnsiColor::Navy.into())),
SgrCode::ForegroundBrightRed => one!(Sgr::Foreground(AnsiColor::Red.into())), SgrCode::ForegroundMagenta => {
SgrCode::ForegroundBrightGreen => one!(Sgr::Foreground(AnsiColor::Lime.into())), one!(Sgr::Foreground(AnsiColor::Purple.into()))
SgrCode::ForegroundBrightYellow => { }
one!(Sgr::Foreground(AnsiColor::Yellow.into())) SgrCode::ForegroundCyan => one!(Sgr::Foreground(AnsiColor::Teal.into())),
} SgrCode::ForegroundWhite => one!(Sgr::Foreground(AnsiColor::Silver.into())),
SgrCode::ForegroundBrightBlue => one!(Sgr::Foreground(AnsiColor::Blue.into())), SgrCode::ForegroundDefault => one!(Sgr::Foreground(ColorSpec::Default)),
SgrCode::ForegroundBrightMagenta => { SgrCode::ForegroundBrightBlack => {
one!(Sgr::Foreground(AnsiColor::Fuschia.into())) one!(Sgr::Foreground(AnsiColor::Grey.into()))
} }
SgrCode::ForegroundBrightCyan => one!(Sgr::Foreground(AnsiColor::Aqua.into())), SgrCode::ForegroundBrightRed => {
SgrCode::ForegroundBrightWhite => { one!(Sgr::Foreground(AnsiColor::Red.into()))
one!(Sgr::Foreground(AnsiColor::White.into())) }
} SgrCode::ForegroundBrightGreen => {
one!(Sgr::Foreground(AnsiColor::Lime.into()))
}
SgrCode::ForegroundBrightYellow => {
one!(Sgr::Foreground(AnsiColor::Yellow.into()))
}
SgrCode::ForegroundBrightBlue => {
one!(Sgr::Foreground(AnsiColor::Blue.into()))
}
SgrCode::ForegroundBrightMagenta => {
one!(Sgr::Foreground(AnsiColor::Fuschia.into()))
}
SgrCode::ForegroundBrightCyan => {
one!(Sgr::Foreground(AnsiColor::Aqua.into()))
}
SgrCode::ForegroundBrightWhite => {
one!(Sgr::Foreground(AnsiColor::White.into()))
}
SgrCode::BackgroundColor => self.parse_sgr_color(params).map(Sgr::Background), SgrCode::BackgroundColor => {
SgrCode::BackgroundBlack => one!(Sgr::Background(AnsiColor::Black.into())), self.parse_sgr_color(params).map(Sgr::Background)
SgrCode::BackgroundRed => one!(Sgr::Background(AnsiColor::Maroon.into())), }
SgrCode::BackgroundGreen => one!(Sgr::Background(AnsiColor::Green.into())), SgrCode::BackgroundBlack => one!(Sgr::Background(AnsiColor::Black.into())),
SgrCode::BackgroundYellow => one!(Sgr::Background(AnsiColor::Olive.into())), SgrCode::BackgroundRed => one!(Sgr::Background(AnsiColor::Maroon.into())),
SgrCode::BackgroundBlue => one!(Sgr::Background(AnsiColor::Navy.into())), SgrCode::BackgroundGreen => one!(Sgr::Background(AnsiColor::Green.into())),
SgrCode::BackgroundMagenta => one!(Sgr::Background(AnsiColor::Purple.into())), SgrCode::BackgroundYellow => one!(Sgr::Background(AnsiColor::Olive.into())),
SgrCode::BackgroundCyan => one!(Sgr::Background(AnsiColor::Teal.into())), SgrCode::BackgroundBlue => one!(Sgr::Background(AnsiColor::Navy.into())),
SgrCode::BackgroundWhite => one!(Sgr::Background(AnsiColor::Silver.into())), SgrCode::BackgroundMagenta => {
SgrCode::BackgroundDefault => one!(Sgr::Background(ColorSpec::Default)), one!(Sgr::Background(AnsiColor::Purple.into()))
SgrCode::BackgroundBrightBlack => one!(Sgr::Background(AnsiColor::Grey.into())), }
SgrCode::BackgroundBrightRed => one!(Sgr::Background(AnsiColor::Red.into())), SgrCode::BackgroundCyan => one!(Sgr::Background(AnsiColor::Teal.into())),
SgrCode::BackgroundBrightGreen => one!(Sgr::Background(AnsiColor::Lime.into())), SgrCode::BackgroundWhite => one!(Sgr::Background(AnsiColor::Silver.into())),
SgrCode::BackgroundBrightYellow => { SgrCode::BackgroundDefault => one!(Sgr::Background(ColorSpec::Default)),
one!(Sgr::Background(AnsiColor::Yellow.into())) SgrCode::BackgroundBrightBlack => {
} one!(Sgr::Background(AnsiColor::Grey.into()))
SgrCode::BackgroundBrightBlue => one!(Sgr::Background(AnsiColor::Blue.into())), }
SgrCode::BackgroundBrightMagenta => { SgrCode::BackgroundBrightRed => {
one!(Sgr::Background(AnsiColor::Fuschia.into())) one!(Sgr::Background(AnsiColor::Red.into()))
} }
SgrCode::BackgroundBrightCyan => one!(Sgr::Background(AnsiColor::Aqua.into())), SgrCode::BackgroundBrightGreen => {
SgrCode::BackgroundBrightWhite => { one!(Sgr::Background(AnsiColor::Lime.into()))
one!(Sgr::Background(AnsiColor::White.into())) }
} SgrCode::BackgroundBrightYellow => {
one!(Sgr::Background(AnsiColor::Yellow.into()))
}
SgrCode::BackgroundBrightBlue => {
one!(Sgr::Background(AnsiColor::Blue.into()))
}
SgrCode::BackgroundBrightMagenta => {
one!(Sgr::Background(AnsiColor::Fuschia.into()))
}
SgrCode::BackgroundBrightCyan => {
one!(Sgr::Background(AnsiColor::Aqua.into()))
}
SgrCode::BackgroundBrightWhite => {
one!(Sgr::Background(AnsiColor::White.into()))
}
SgrCode::InverseOn => one!(Sgr::Inverse(true)), SgrCode::InverseOn => one!(Sgr::Inverse(true)),
SgrCode::InverseOff => one!(Sgr::Inverse(false)), SgrCode::InverseOff => one!(Sgr::Inverse(false)),
SgrCode::InvisibleOn => one!(Sgr::Invisible(true)), SgrCode::InvisibleOn => one!(Sgr::Invisible(true)),
SgrCode::InvisibleOff => one!(Sgr::Invisible(false)), SgrCode::InvisibleOff => one!(Sgr::Invisible(false)),
SgrCode::StrikeThroughOn => one!(Sgr::StrikeThrough(true)), SgrCode::StrikeThroughOn => one!(Sgr::StrikeThrough(true)),
SgrCode::StrikeThroughOff => one!(Sgr::StrikeThrough(false)), SgrCode::StrikeThroughOff => one!(Sgr::StrikeThrough(false)),
SgrCode::OverlineOn => one!(Sgr::Overline(true)), SgrCode::OverlineOn => one!(Sgr::Overline(true)),
SgrCode::OverlineOff => one!(Sgr::Overline(false)), SgrCode::OverlineOff => one!(Sgr::Overline(false)),
SgrCode::DefaultFont => one!(Sgr::Font(Font::Default)), SgrCode::DefaultFont => one!(Sgr::Font(Font::Default)),
SgrCode::AltFont1 => one!(Sgr::Font(Font::Alternate(1))), SgrCode::AltFont1 => one!(Sgr::Font(Font::Alternate(1))),
SgrCode::AltFont2 => one!(Sgr::Font(Font::Alternate(2))), SgrCode::AltFont2 => one!(Sgr::Font(Font::Alternate(2))),
SgrCode::AltFont3 => one!(Sgr::Font(Font::Alternate(3))), SgrCode::AltFont3 => one!(Sgr::Font(Font::Alternate(3))),
SgrCode::AltFont4 => one!(Sgr::Font(Font::Alternate(4))), SgrCode::AltFont4 => one!(Sgr::Font(Font::Alternate(4))),
SgrCode::AltFont5 => one!(Sgr::Font(Font::Alternate(5))), SgrCode::AltFont5 => one!(Sgr::Font(Font::Alternate(5))),
SgrCode::AltFont6 => one!(Sgr::Font(Font::Alternate(6))), SgrCode::AltFont6 => one!(Sgr::Font(Font::Alternate(6))),
SgrCode::AltFont7 => one!(Sgr::Font(Font::Alternate(7))), SgrCode::AltFont7 => one!(Sgr::Font(Font::Alternate(7))),
SgrCode::AltFont8 => one!(Sgr::Font(Font::Alternate(8))), SgrCode::AltFont8 => one!(Sgr::Font(Font::Alternate(8))),
SgrCode::AltFont9 => one!(Sgr::Font(Font::Alternate(9))), SgrCode::AltFont9 => one!(Sgr::Font(Font::Alternate(9))),
},
}, },
CsiParam::ColonList(_) => Err(()),
} }
} }
} }
@ -2028,14 +2088,22 @@ mod test {
use std::io::Write; use std::io::Write;
fn parse(control: char, params: &[i64], expected: &str) -> Vec<CSI> { fn parse(control: char, params: &[i64], expected: &str) -> Vec<CSI> {
let res = CSI::parse(params, &[], false, control).collect(); let params = params
.iter()
.map(|&i| CsiParam::Integer(i))
.collect::<Vec<_>>();
let res = CSI::parse(&params, &[], false, control).collect();
assert_eq!(encode(&res), expected); assert_eq!(encode(&res), expected);
res res
} }
fn parse_int(control: char, params: &[i64], intermediate: u8, expected: &str) -> Vec<CSI> { fn parse_int(control: char, params: &[i64], intermediate: u8, expected: &str) -> Vec<CSI> {
let params = params
.iter()
.map(|&i| CsiParam::Integer(i))
.collect::<Vec<_>>();
let intermediates = [intermediate]; let intermediates = [intermediate];
let res = CSI::parse(params, &intermediates, false, control).collect(); let res = CSI::parse(&params, &intermediates, false, control).collect();
assert_eq!(encode(&res), expected); assert_eq!(encode(&res), expected);
res res
} }
@ -2072,7 +2140,7 @@ mod test {
CSI::Sgr(Sgr::Intensity(Intensity::Bold)), CSI::Sgr(Sgr::Intensity(Intensity::Bold)),
CSI::Sgr(Sgr::Italic(true)), CSI::Sgr(Sgr::Italic(true)),
CSI::Unspecified(Box::new(Unspecified { CSI::Unspecified(Box::new(Unspecified {
params: [1231231].to_vec(), params: [CsiParam::Integer(1231231)].to_vec(),
intermediates: vec![], intermediates: vec![],
ignored_extra_intermediates: false, ignored_extra_intermediates: false,
control: 'm', control: 'm',
@ -2084,7 +2152,7 @@ mod test {
vec![ vec![
CSI::Sgr(Sgr::Intensity(Intensity::Bold)), CSI::Sgr(Sgr::Intensity(Intensity::Bold)),
CSI::Unspecified(Box::new(Unspecified { CSI::Unspecified(Box::new(Unspecified {
params: [1231231, 3].to_vec(), params: [CsiParam::Integer(1231231), CsiParam::Integer(3)].to_vec(),
intermediates: vec![], intermediates: vec![],
ignored_extra_intermediates: false, ignored_extra_intermediates: false,
control: 'm', control: 'm',
@ -2094,7 +2162,7 @@ mod test {
assert_eq!( assert_eq!(
parse('m', &[1231231, 3], "\x1b[1231231;3m"), parse('m', &[1231231, 3], "\x1b[1231231;3m"),
vec![CSI::Unspecified(Box::new(Unspecified { vec![CSI::Unspecified(Box::new(Unspecified {
params: [1231231, 3].to_vec(), params: [CsiParam::Integer(1231231), CsiParam::Integer(3)].to_vec(),
intermediates: vec![], intermediates: vec![],
ignored_extra_intermediates: false, ignored_extra_intermediates: false,
control: 'm', control: 'm',
@ -2119,7 +2187,7 @@ mod test {
assert_eq!( assert_eq!(
parse('m', &[58, 2], "\x1b[58;2m"), parse('m', &[58, 2], "\x1b[58;2m"),
vec![CSI::Unspecified(Box::new(Unspecified { vec![CSI::Unspecified(Box::new(Unspecified {
params: [58, 2].to_vec(), params: [CsiParam::Integer(58), CsiParam::Integer(2)].to_vec(),
intermediates: vec![], intermediates: vec![],
ignored_extra_intermediates: false, ignored_extra_intermediates: false,
control: 'm', control: 'm',
@ -2137,7 +2205,7 @@ mod test {
vec![ vec![
CSI::Sgr(Sgr::UnderlineColor(ColorSpec::PaletteIndex(220))), CSI::Sgr(Sgr::UnderlineColor(ColorSpec::PaletteIndex(220))),
CSI::Unspecified(Box::new(Unspecified { CSI::Unspecified(Box::new(Unspecified {
params: [255, 255].to_vec(), params: [CsiParam::Integer(255), CsiParam::Integer(255)].to_vec(),
intermediates: vec![], intermediates: vec![],
ignored_extra_intermediates: false, ignored_extra_intermediates: false,
control: 'm', control: 'm',
@ -2151,7 +2219,7 @@ mod test {
assert_eq!( assert_eq!(
parse('m', &[38, 2], "\x1b[38;2m"), parse('m', &[38, 2], "\x1b[38;2m"),
vec![CSI::Unspecified(Box::new(Unspecified { vec![CSI::Unspecified(Box::new(Unspecified {
params: [38, 2].to_vec(), params: [CsiParam::Integer(38), CsiParam::Integer(2)].to_vec(),
intermediates: vec![], intermediates: vec![],
ignored_extra_intermediates: false, ignored_extra_intermediates: false,
control: 'm', control: 'm',
@ -2169,7 +2237,7 @@ mod test {
vec![ vec![
CSI::Sgr(Sgr::Foreground(ColorSpec::PaletteIndex(220))), CSI::Sgr(Sgr::Foreground(ColorSpec::PaletteIndex(220))),
CSI::Unspecified(Box::new(Unspecified { CSI::Unspecified(Box::new(Unspecified {
params: [255, 255].to_vec(), params: [CsiParam::Integer(255), CsiParam::Integer(255)].to_vec(),
intermediates: vec![], intermediates: vec![],
ignored_extra_intermediates: false, ignored_extra_intermediates: false,
control: 'm', control: 'm',

View File

@ -18,6 +18,8 @@ pub use self::esc::Esc;
pub use self::esc::EscCode; pub use self::esc::EscCode;
pub use self::osc::OperatingSystemCommand; pub use self::osc::OperatingSystemCommand;
use vtparse::CsiParam;
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum Action { pub enum Action {
/// Send a single printable character to the display /// Send a single printable character to the display
@ -477,35 +479,35 @@ impl OneBased {
/// Map a value from an escape sequence parameter. /// Map a value from an escape sequence parameter.
/// 0 is equivalent to 1 /// 0 is equivalent to 1
pub fn from_esc_param(v: i64) -> Result<Self, ()> { pub fn from_esc_param(v: &CsiParam) -> Result<Self, ()> {
if v == 0 { match v {
Ok(Self { CsiParam::Integer(v) if *v == 0 => Ok(Self {
value: num_traits::one(), value: num_traits::one(),
}) }),
} else if v > 0 && v <= i64::from(u32::max_value()) { CsiParam::Integer(v) if *v > 0 && *v <= i64::from(u32::max_value()) => {
Ok(Self { value: v as u32 }) Ok(Self { value: *v as u32 })
} else { }
Err(()) _ => Err(()),
} }
} }
/// Map a value from an escape sequence parameter. /// Map a value from an escape sequence parameter.
/// 0 is equivalent to max_value. /// 0 is equivalent to max_value.
pub fn from_esc_param_with_big_default(v: i64) -> Result<Self, ()> { pub fn from_esc_param_with_big_default(v: &CsiParam) -> Result<Self, ()> {
if v == 0 { match v {
Ok(Self { CsiParam::Integer(v) if *v == 0 => Ok(Self {
value: u32::max_value(), value: u32::max_value(),
}) }),
} else if v > 0 && v <= i64::from(u32::max_value()) { CsiParam::Integer(v) if *v > 0 && *v <= i64::from(u32::max_value()) => {
Ok(Self { value: v as u32 }) Ok(Self { value: *v as u32 })
} else { }
Err(()) _ => Err(()),
} }
} }
/// Map a value from an optional escape sequence parameter /// Map a value from an optional escape sequence parameter
pub fn from_optional_esc_param(o: Option<&i64>) -> Result<Self, ()> { pub fn from_optional_esc_param(o: Option<&CsiParam>) -> Result<Self, ()> {
Self::from_esc_param(o.cloned().unwrap_or(1)) Self::from_esc_param(o.unwrap_or(&CsiParam::Integer(1)))
} }
/// Return the underlying value as a 0-based value /// Return the underlying value as a 0-based value

View File

@ -7,7 +7,7 @@ use log::error;
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
use regex::bytes::Regex; use regex::bytes::Regex;
use std::cell::RefCell; use std::cell::RefCell;
use vtparse::{VTActor, VTParser}; use vtparse::{CsiParam, VTActor, VTParser};
struct SixelBuilder { struct SixelBuilder {
sixel: Sixel, sixel: Sixel,
@ -213,7 +213,7 @@ impl<'a, F: FnMut(Action)> VTActor for Performer<'a, F> {
fn csi_dispatch( fn csi_dispatch(
&mut self, &mut self,
params: &[i64], params: &[CsiParam],
intermediates: &[u8], intermediates: &[u8],
ignored_extra_intermediates: bool, ignored_extra_intermediates: bool,
control: u8, control: u8,
@ -482,12 +482,17 @@ mod test {
fn fancy_underline() { fn fancy_underline() {
let mut p = Parser::new(); let mut p = Parser::new();
// Kitty underline sequences use a `:` which is explicitly invalid
// and deleted by the dec/ansi vtparser
let actions = p.parse_as_vec(b"\x1b[4:0mb"); let actions = p.parse_as_vec(b"\x1b[4:0mb");
assert_eq!( assert_eq!(
vec![ vec![
// NO: Action::CSI(CSI::Sgr(Sgr::Underline(Underline::None))), Action::CSI(CSI::Unspecified(Box::new(
crate::escape::csi::Unspecified {
params: vec![CsiParam::ColonList(vec![Some(4), Some(0)])],
intermediates: vec![],
ignored_extra_intermediates: false,
control: 'm'
}
))),
Action::Print('b'), Action::Print('b'),
], ],
actions actions

View File

@ -1,7 +1,7 @@
[package] [package]
authors = ["Wez Furlong <wez@wezfurlong.org>"] authors = ["Wez Furlong <wez@wezfurlong.org>"]
name = "vtparse" name = "vtparse"
version = "0.3.0" version = "0.4.0"
edition = "2018" edition = "2018"
repository = "https://github.com/wez/wezterm" repository = "https://github.com/wez/wezterm"
description = "Low level escape sequence parser" description = "Low level escape sequence parser"

View File

@ -151,7 +151,7 @@ pub trait VTActor {
/// for more information on control functions. /// for more information on control functions.
fn csi_dispatch( fn csi_dispatch(
&mut self, &mut self,
params: &[i64], params: &[CsiParam],
intermediates: &[u8], intermediates: &[u8],
ignored_excess_intermediates: bool, ignored_excess_intermediates: bool,
byte: u8, byte: u8,
@ -187,7 +187,7 @@ pub enum VTAction {
byte: u8, byte: u8,
}, },
CsiDispatch { CsiDispatch {
params: Vec<i64>, params: Vec<CsiParam>,
intermediates: Vec<u8>, intermediates: Vec<u8>,
ignored_excess_intermediates: bool, ignored_excess_intermediates: bool,
byte: u8, byte: u8,
@ -268,7 +268,7 @@ impl VTActor for CollectingVTActor {
fn csi_dispatch( fn csi_dispatch(
&mut self, &mut self,
params: &[i64], params: &[CsiParam],
intermediates: &[u8], intermediates: &[u8],
ignored_excess_intermediates: bool, ignored_excess_intermediates: bool,
byte: u8, byte: u8,
@ -333,20 +333,84 @@ pub struct VTParser {
osc: OscState, osc: OscState,
params: [i64; MAX_PARAMS], params: [CsiParam; MAX_PARAMS],
num_params: usize, num_params: usize,
current_param: Option<i64>, current_param: Option<CsiParam>,
params_full: bool, params_full: bool,
utf8_parser: Utf8Parser, utf8_parser: Utf8Parser,
utf8_return_state: State, utf8_return_state: State,
} }
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
pub enum CsiParam {
Integer(i64),
ColonList(Vec<Option<i64>>),
// TODO: add a None case here, but that requires more care
}
impl Default for CsiParam {
fn default() -> Self {
Self::Integer(0)
}
}
fn add_digit(target: &mut i64, digit: u8) {
*target = target
.saturating_mul(10)
.saturating_add((digit - b'0') as i64);
}
impl CsiParam {
fn add_digit(&mut self, digit: u8) {
match self {
Self::Integer(i) => add_digit(i, digit),
Self::ColonList(list) => {
if let Some(target) = list.last_mut().unwrap() {
add_digit(target, digit);
} else {
// Promote trailing None into a 0 value
let mut target = 0;
add_digit(&mut target, digit);
list.last_mut().unwrap().replace(target);
}
}
}
}
pub fn as_integer(&self) -> Option<i64> {
match self {
Self::Integer(i) => Some(*i),
_ => None,
}
}
}
impl std::fmt::Display for CsiParam {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
CsiParam::Integer(v) => {
write!(f, "{}", v)?;
}
CsiParam::ColonList(list) => {
for (idx, p) in list.iter().enumerate() {
if idx > 0 {
write!(f, ":")?;
}
if let Some(num) = p {
write!(f, "{}", num)?;
}
}
}
}
Ok(())
}
}
impl VTParser { impl VTParser {
#[allow(clippy::new_without_default)] #[allow(clippy::new_without_default)]
pub fn new() -> Self { pub fn new() -> Self {
let param_indices = [0usize; MAX_OSC]; let param_indices = [0usize; MAX_OSC];
let params = [0i64; MAX_PARAMS];
Self { Self {
state: State::Ground, state: State::Ground,
@ -363,7 +427,7 @@ impl VTParser {
full: false, full: false,
}, },
params, params: Default::default(),
num_params: 0, num_params: 0,
params_full: false, params_full: false,
current_param: None, current_param: None,
@ -372,6 +436,20 @@ impl VTParser {
} }
} }
fn as_integer_params(&self) -> [i64; MAX_PARAMS] {
let mut res = [0i64; MAX_PARAMS];
for (src, dest) in self.params[0..self.num_params]
.iter()
.zip(&mut res[0..self.num_params])
{
match src {
CsiParam::Integer(i) => *dest = *i,
bad => panic!("illegal parameter type: {:?}", bad),
}
}
res
}
fn finish_param(&mut self) { fn finish_param(&mut self) {
if let Some(val) = self.current_param.take() { if let Some(val) = self.current_param.take() {
if self.num_params < MAX_PARAMS { if self.num_params < MAX_PARAMS {
@ -411,24 +489,34 @@ impl VTParser {
if self.num_params + 1 > MAX_OSC { if self.num_params + 1 > MAX_OSC {
self.params_full = true; self.params_full = true;
} else { } else {
self.params[self.num_params] = self.current_param.take().unwrap_or(0); // FIXME: the unwrap_or here is a lossy representation.
// Consider replacing this with a CsiParam::None variant
// to indicate that it wasn't present?
self.params[self.num_params] =
self.current_param.take().unwrap_or(CsiParam::Integer(0));
self.num_params += 1; self.num_params += 1;
} }
} else { } else if param == b':' {
let current = self.current_param.take().unwrap_or(0); let mut current = match self.current_param.take() {
None => vec![None],
Some(CsiParam::Integer(i)) => vec![Some(i)],
Some(CsiParam::ColonList(list)) => list,
};
self.current_param.replace( current.push(None); // Start a new, empty parameter
current
.saturating_mul(10) self.current_param.replace(CsiParam::ColonList(current));
.saturating_add((param - b'0') as i64), } else {
); let mut current = self.current_param.take().unwrap_or(CsiParam::Integer(0));
current.add_digit(param);
self.current_param.replace(current);
} }
} }
Action::Hook => { Action::Hook => {
self.finish_param(); self.finish_param();
actor.dcs_hook( actor.dcs_hook(
param, param,
&self.params[0..self.num_params], &self.as_integer_params()[0..self.num_params],
&self.intermediates[0..self.num_intermediates], &self.intermediates[0..self.num_intermediates],
self.ignored_excess_intermediates, self.ignored_excess_intermediates,
); );
@ -437,7 +525,7 @@ impl VTParser {
Action::EscDispatch => { Action::EscDispatch => {
self.finish_param(); self.finish_param();
actor.esc_dispatch( actor.esc_dispatch(
&self.params[0..self.num_params], &self.as_integer_params()[0..self.num_params],
&self.intermediates[0..self.num_intermediates], &self.intermediates[0..self.num_intermediates],
self.ignored_excess_intermediates, self.ignored_excess_intermediates,
param, param,
@ -599,7 +687,7 @@ mod test {
VTAction::Print('o'), VTAction::Print('o'),
VTAction::ExecuteC0orC1(0x07,), VTAction::ExecuteC0orC1(0x07,),
VTAction::CsiDispatch { VTAction::CsiDispatch {
params: vec![32], params: vec![CsiParam::Integer(32)],
intermediates: vec![], intermediates: vec![],
ignored_excess_intermediates: false, ignored_excess_intermediates: false,
byte: b'm', byte: b'm',
@ -609,7 +697,7 @@ mod test {
VTAction::Print('o',), VTAction::Print('o',),
VTAction::Print('t',), VTAction::Print('t',),
VTAction::CsiDispatch { VTAction::CsiDispatch {
params: vec![0], params: vec![CsiParam::Integer(0)],
intermediates: vec![], intermediates: vec![],
ignored_excess_intermediates: false, ignored_excess_intermediates: false,
byte: b'm', byte: b'm',
@ -712,7 +800,7 @@ mod test {
assert_eq!( assert_eq!(
parse_as_vec(b"\x1b[4m"), parse_as_vec(b"\x1b[4m"),
vec![VTAction::CsiDispatch { vec![VTAction::CsiDispatch {
params: vec![4], params: vec![CsiParam::Integer(4)],
intermediates: b"".to_vec(), intermediates: b"".to_vec(),
ignored_excess_intermediates: false, ignored_excess_intermediates: false,
byte: b'm' byte: b'm'
@ -721,11 +809,33 @@ mod test {
assert_eq!( assert_eq!(
// This is the kitty curly underline sequence. // This is the kitty curly underline sequence.
// The : is explicitly set to be ignored by
// the state machine tables, so this whole sequence
// is discarded during parsing.
parse_as_vec(b"\x1b[4:3m"), parse_as_vec(b"\x1b[4:3m"),
vec![] vec![VTAction::CsiDispatch {
params: vec![CsiParam::ColonList(vec![Some(4), Some(3)])],
intermediates: b"".to_vec(),
ignored_excess_intermediates: false,
byte: b'm'
}]
);
}
#[test]
fn test_colon_rgb() {
assert_eq!(
parse_as_vec(b"\x1b[38:2::128:64:192m"),
vec![VTAction::CsiDispatch {
params: vec![CsiParam::ColonList(vec![
Some(38),
Some(2),
None,
Some(128),
Some(64),
Some(192)
])],
intermediates: b"".to_vec(),
ignored_excess_intermediates: false,
byte: b'm'
}]
); );
} }
@ -735,7 +845,7 @@ mod test {
parse_as_vec(b"\x1b[;1m"), parse_as_vec(b"\x1b[;1m"),
vec![VTAction::CsiDispatch { vec![VTAction::CsiDispatch {
// The omitted parameter defaults to 0 // The omitted parameter defaults to 0
params: vec![0, 1], params: vec![CsiParam::Integer(0), CsiParam::Integer(1)],
intermediates: b"".to_vec(), intermediates: b"".to_vec(),
ignored_excess_intermediates: false, ignored_excess_intermediates: false,
byte: b'm' byte: b'm'
@ -748,7 +858,10 @@ mod test {
assert_eq!( assert_eq!(
parse_as_vec(b"\x1b[0;1;2;3;4;5;6;7;8;9;0;1;2;3;4;51;6p"), parse_as_vec(b"\x1b[0;1;2;3;4;5;6;7;8;9;0;1;2;3;4;51;6p"),
vec![VTAction::CsiDispatch { vec![VTAction::CsiDispatch {
params: vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 51], params: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 51]
.iter()
.map(|&i| CsiParam::Integer(i))
.collect(),
intermediates: b"".to_vec(), intermediates: b"".to_vec(),
ignored_excess_intermediates: false, ignored_excess_intermediates: false,
byte: b'p' byte: b'p'
@ -761,7 +874,7 @@ mod test {
assert_eq!( assert_eq!(
parse_as_vec(b"\x1b[1 p"), parse_as_vec(b"\x1b[1 p"),
vec![VTAction::CsiDispatch { vec![VTAction::CsiDispatch {
params: vec![1], params: vec![CsiParam::Integer(1)],
intermediates: b" ".to_vec(), intermediates: b" ".to_vec(),
ignored_excess_intermediates: false, ignored_excess_intermediates: false,
byte: b'p' byte: b'p'
@ -770,7 +883,7 @@ mod test {
assert_eq!( assert_eq!(
parse_as_vec(b"\x1b[1 !p"), parse_as_vec(b"\x1b[1 !p"),
vec![VTAction::CsiDispatch { vec![VTAction::CsiDispatch {
params: vec![1], params: vec![CsiParam::Integer(1)],
intermediates: b" !".to_vec(), intermediates: b" !".to_vec(),
ignored_excess_intermediates: false, ignored_excess_intermediates: false,
byte: b'p' byte: b'p'
@ -779,7 +892,7 @@ mod test {
assert_eq!( assert_eq!(
parse_as_vec(b"\x1b[1 !#p"), parse_as_vec(b"\x1b[1 !#p"),
vec![VTAction::CsiDispatch { vec![VTAction::CsiDispatch {
params: vec![1], params: vec![CsiParam::Integer(1)],
// Note that the `#` was discarded // Note that the `#` was discarded
intermediates: b" !".to_vec(), intermediates: b" !".to_vec(),
ignored_excess_intermediates: true, ignored_excess_intermediates: true,

View File

@ -194,10 +194,8 @@ define_function! {
0x00..=0x17 => (Execute, CsiParam), 0x00..=0x17 => (Execute, CsiParam),
0x19 => (Execute, CsiParam), 0x19 => (Execute, CsiParam),
0x1c..=0x1f => (Execute, CsiParam), 0x1c..=0x1f => (Execute, CsiParam),
0x30..=0x39 => (Param, CsiParam), 0x30..=0x3b => (Param, CsiParam),
0x3b => (Param, CsiParam),
0x7f => (Ignore, CsiParam), 0x7f => (Ignore, CsiParam),
0x3a => (None, CsiIgnore),
0x3c..=0x3f => (None, CsiIgnore), 0x3c..=0x3f => (None, CsiIgnore),
0x20..=0x2f => (Collect, CsiIntermediate), 0x20..=0x2f => (Collect, CsiIntermediate),
0x40..=0x7e => (CsiDispatch, Ground), 0x40..=0x7e => (CsiDispatch, Ground),