From b6a422a5423953ac87f81957ea6dad610262af9d Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Wed, 6 Jan 2021 16:58:58 -0800 Subject: [PATCH] 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 --- Cargo.lock | 2 +- termwiz/Cargo.toml | 2 +- termwiz/src/escape/csi.rs | 572 +++++++++++++++++-------------- termwiz/src/escape/mod.rs | 38 +- termwiz/src/escape/parser/mod.rs | 15 +- vtparse/Cargo.toml | 2 +- vtparse/src/lib.rs | 171 +++++++-- vtparse/src/transitions.rs | 4 +- 8 files changed, 496 insertions(+), 310 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f4ef46616..a618a57a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4005,7 +4005,7 @@ dependencies = [ [[package]] name = "vtparse" -version = "0.3.0" +version = "0.4.0" dependencies = [ "pretty_assertions", "utf8parse", diff --git a/termwiz/Cargo.toml b/termwiz/Cargo.toml index 0371e021a..731ccf598 100644 --- a/termwiz/Cargo.toml +++ b/termwiz/Cargo.toml @@ -30,7 +30,7 @@ terminfo = "0.7" unicode-segmentation = "1.7" unicode-width = "0.1" xi-unicode = "0.3" -vtparse = { version="0.3", path="../vtparse" } +vtparse = { version="0.4", path="../vtparse" } [features] widgets = ["cassowary", "fnv"] diff --git a/termwiz/src/escape/csi.rs b/termwiz/src/escape/csi.rs index a459b7949..0d0646236 100644 --- a/termwiz/src/escape/csi.rs +++ b/termwiz/src/escape/csi.rs @@ -6,6 +6,8 @@ use num_derive::*; use num_traits::{FromPrimitive, ToPrimitive}; use std::fmt::{Display, Error as FmtError, Formatter}; +pub use vtparse::CsiParam; + #[derive(Debug, Clone, PartialEq, Eq)] pub enum CSI { /// SGR: Set Graphics Rendition. @@ -33,15 +35,15 @@ pub enum CSI { #[derive(Debug, Clone, PartialEq, Eq)] pub struct Unspecified { - params: Vec, + pub params: Vec, // TODO: can we just make intermediates a single u8? - intermediates: Vec, + pub intermediates: Vec, /// if true, more than two intermediates arrived and the /// remaining data was ignored - ignored_extra_intermediates: bool, + pub ignored_extra_intermediates: bool, /// The final character in the CSI sequence; this typically /// defines how to interpret the other parameters. - control: char, + pub control: char, } impl Display for Unspecified { @@ -117,7 +119,7 @@ pub enum DeviceAttributeCodes { #[derive(Debug, Clone, PartialEq, Eq)] pub enum DeviceAttribute { Code(DeviceAttributeCodes), - Unspecified(u16), + Unspecified(CsiParam), } #[derive(Debug, Clone, PartialEq, Eq)] @@ -131,7 +133,7 @@ impl DeviceAttributeFlags { for item in &self.attributes { match item { 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")?; @@ -142,12 +144,15 @@ impl DeviceAttributeFlags { Self { attributes } } - fn from_params(params: &[i64]) -> Self { + fn from_params(params: &[CsiParam]) -> Self { let mut attributes = Vec::new(); - for p in params { - match FromPrimitive::from_i64(*p) { - Some(c) => attributes.push(DeviceAttribute::Code(c)), - None => attributes.push(DeviceAttribute::Unspecified(*p as u16)), + for i in params { + match i { + CsiParam::Integer(p) => match FromPrimitive::from_i64(*p) { + Some(c) => attributes.push(DeviceAttribute::Code(c)), + None => attributes.push(DeviceAttribute::Unspecified(i.clone())), + }, + _ => attributes.push(DeviceAttribute::Unspecified(i.clone())), } } Self { attributes } @@ -941,16 +946,16 @@ impl Display for Cursor { /// but in some we build out an enum. The trait helps to generalize /// the parser code while keeping it relatively terse. trait ParseParams: Sized { - fn parse_params(params: &[i64]) -> Result; + fn parse_params(params: &[CsiParam]) -> Result; } /// Parse an input parameter into a 1-based unsigned value impl ParseParams for u32 { - fn parse_params(params: &[i64]) -> Result { + fn parse_params(params: &[CsiParam]) -> Result { if params.is_empty() { Ok(1) } else if params.len() == 1 { - to_1b_u32(params[0]) + to_1b_u32(¶ms[0]) } else { Err(()) } @@ -959,11 +964,11 @@ impl ParseParams for u32 { /// Parse an input parameter into a 1-based unsigned value impl ParseParams for OneBased { - fn parse_params(params: &[i64]) -> Result { + fn parse_params(params: &[CsiParam]) -> Result { if params.is_empty() { Ok(OneBased::new(1)) } else if params.len() == 1 { - OneBased::from_esc_param(params[0]) + OneBased::from_esc_param(¶ms[0]) } else { Err(()) } @@ -974,15 +979,15 @@ impl ParseParams for OneBased { /// This is typically used to build a struct comprised of /// the pair of values. 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() { Ok((OneBased::new(1), OneBased::new(1))) } else if params.len() == 1 { - Ok((OneBased::from_esc_param(params[0])?, OneBased::new(1))) + Ok((OneBased::from_esc_param(¶ms[0])?, OneBased::new(1))) } else if params.len() == 2 { Ok(( - OneBased::from_esc_param(params[0])?, - OneBased::from_esc_param(params[1])?, + OneBased::from_esc_param(¶ms[0])?, + OneBased::from_esc_param(¶ms[1])?, )) } else { Err(()) @@ -1000,11 +1005,14 @@ trait ParamEnum: FromPrimitive { /// implement ParseParams for the enums that also implement ParamEnum. impl ParseParams for T { - fn parse_params(params: &[i64]) -> Result { + fn parse_params(params: &[CsiParam]) -> Result { if params.is_empty() { Ok(ParamEnum::default()) } 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 { Err(()) } @@ -1257,7 +1265,7 @@ struct CSIParser<'a> { /// 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 /// to update params to an empty slice. - params: Option<&'a [i64]>, + params: Option<&'a [CsiParam]>, } impl CSI { @@ -1268,7 +1276,7 @@ impl CSI { /// If no semantic meaning is known for a subsequence, the remainder /// of the sequence is returned wrapped in a `CSI::Unspecified` container. pub fn parse<'a>( - params: &'a [i64], + params: &'a [CsiParam], intermediates: &'a [u8], ignored_extra_intermediates: bool, control: char, @@ -1283,11 +1291,16 @@ impl CSI { } /// A little helper to convert i64 -> u8 if safe -fn to_u8(v: i64) -> Result { - if v <= i64::from(u8::max_value()) { - Ok(v as u8) - } else { - Err(()) +fn to_u8(v: &CsiParam) -> Result { + match v { + CsiParam::ColonList(_) => Err(()), + CsiParam::Integer(v) => { + if *v <= i64::from(u8::max_value()) { + Ok(*v as u8) + } else { + Err(()) + } + } } } @@ -1302,13 +1315,11 @@ fn to_u8(v: i64) -> Result { /// otherwise outside that range, an error is propagated and /// that will typically case the sequence to be reported via /// the Unspecified placeholder. -fn to_1b_u32(v: i64) -> Result { - if v == 0 { - Ok(1) - } else if v > 0 && v <= i64::from(u32::max_value()) { - Ok(v as u32) - } else { - Err(()) +fn to_1b_u32(v: &CsiParam) -> Result { + match v { + CsiParam::Integer(v) if *v == 0 => Ok(1), + CsiParam::Integer(v) if *v > 0 && *v <= i64::from(u32::max_value()) => Ok(*v as u32), + _ => Err(()), } } @@ -1338,7 +1349,7 @@ macro_rules! parse { } impl<'a> CSIParser<'a> { - fn parse_next(&mut self, params: &'a [i64]) -> Result { + fn parse_next(&mut self, params: &'a [CsiParam]) -> Result { match (self.control, self.intermediates) { ('@', &[]) => parse!(Edit, InsertCharacter, params), ('`', &[]) => parse!(Cursor, CharacterPositionAbsolute, params), @@ -1387,8 +1398,8 @@ impl<'a> CSIParser<'a> { ('t', &[]) => self.window(params).map(CSI::Window), ('u', &[]) => noparams!(Cursor, RestoreCursor, params), ('y', &[b'*']) => { - fn p(params: &[i64], idx: usize) -> Result { - params.get(idx).cloned().ok_or(()) + fn p(params: &[CsiParam], idx: usize) -> Result { + params.get(idx).and_then(CsiParam::as_integer).ok_or(()) } let request_id = p(params, 0)?; 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 /// as this would trigger returning a default value and/or /// an unterminated parse loop. - fn advance_by(&mut self, n: usize, params: &'a [i64], result: T) -> T { + fn advance_by(&mut self, n: usize, params: &'a [CsiParam], result: T) -> T { let (_, next) = params.split_at(n); if !next.is_empty() { self.params = Some(next); @@ -1453,11 +1464,11 @@ impl<'a> CSIParser<'a> { result } - fn cursor_style(&mut self, params: &'a [i64]) -> Result { + fn cursor_style(&mut self, params: &'a [CsiParam]) -> Result { if params.len() != 1 { Err(()) } else { - match FromPrimitive::from_i64(params[0]) { + match FromPrimitive::from_i64(params[0].as_integer().unwrap()) { None => Err(()), Some(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 { - if params == [5] { + fn dsr(&mut self, params: &'a [CsiParam]) -> Result { + if params == [CsiParam::Integer(5)] { 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))) } else { Err(()) } } - fn decstbm(&mut self, params: &'a [i64]) -> Result { + fn decstbm(&mut self, params: &'a [CsiParam]) -> Result { if params.is_empty() { Ok(CSI::Cursor(Cursor::SetTopAndBottomMargins { top: OneBased::new(1), @@ -1487,7 +1498,7 @@ impl<'a> CSIParser<'a> { 1, params, CSI::Cursor(Cursor::SetTopAndBottomMargins { - top: OneBased::from_esc_param(params[0])?, + top: OneBased::from_esc_param(¶ms[0])?, bottom: OneBased::new(u32::max_value()), }), )) @@ -1496,8 +1507,8 @@ impl<'a> CSIParser<'a> { 2, params, CSI::Cursor(Cursor::SetTopAndBottomMargins { - top: OneBased::from_esc_param(params[0])?, - bottom: OneBased::from_esc_param_with_big_default(params[1])?, + top: OneBased::from_esc_param(¶ms[0])?, + bottom: OneBased::from_esc_param_with_big_default(¶ms[1])?, }), )) } else { @@ -1505,19 +1516,21 @@ impl<'a> CSIParser<'a> { } } - fn xterm_key_modifier(&mut self, params: &'a [i64]) -> Result { + fn xterm_key_modifier(&mut self, params: &'a [CsiParam]) -> Result { 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( 2, params, CSI::Mode(Mode::XtermKeyMode { resource, - value: Some(params[1]), + value: Some(params[1].as_integer().ok_or_else(|| ())?), }), )) } 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( 1, params, @@ -1531,7 +1544,7 @@ impl<'a> CSIParser<'a> { } } - fn decslrm(&mut self, params: &'a [i64]) -> Result { + fn decslrm(&mut self, params: &'a [CsiParam]) -> Result { if params.is_empty() { // with no params this is a request to save the cursor // and is technically in conflict with SetLeftAndRightMargins. @@ -1544,7 +1557,7 @@ impl<'a> CSIParser<'a> { 1, params, CSI::Cursor(Cursor::SetLeftAndRightMargins { - left: OneBased::from_esc_param(params[0])?, + left: OneBased::from_esc_param(¶ms[0])?, right: OneBased::new(u32::max_value()), }), )) @@ -1553,8 +1566,8 @@ impl<'a> CSIParser<'a> { 2, params, CSI::Cursor(Cursor::SetLeftAndRightMargins { - left: OneBased::from_esc_param(params[0])?, - right: OneBased::from_esc_param(params[1])?, + left: OneBased::from_esc_param(¶ms[0])?, + right: OneBased::from_esc_param(¶ms[1])?, }), )) } else { @@ -1562,52 +1575,52 @@ impl<'a> CSIParser<'a> { } } - fn req_primary_device_attributes(&mut self, params: &'a [i64]) -> Result { + fn req_primary_device_attributes(&mut self, params: &'a [CsiParam]) -> Result { if params == [] { Ok(Device::RequestPrimaryDeviceAttributes) - } else if params == [0] { + } else if params == [CsiParam::Integer(0)] { Ok(self.advance_by(1, params, Device::RequestPrimaryDeviceAttributes)) } else { Err(()) } } - fn req_terminal_name_and_version(&mut self, params: &'a [i64]) -> Result { + fn req_terminal_name_and_version(&mut self, params: &'a [CsiParam]) -> Result { if params == [] { Ok(Device::RequestTerminalNameAndVersion) - } else if params == [0] { + } else if params == [CsiParam::Integer(0)] { Ok(self.advance_by(1, params, Device::RequestTerminalNameAndVersion)) } else { Err(()) } } - fn req_secondary_device_attributes(&mut self, params: &'a [i64]) -> Result { + fn req_secondary_device_attributes(&mut self, params: &'a [CsiParam]) -> Result { if params == [] { Ok(Device::RequestSecondaryDeviceAttributes) - } else if params == [0] { + } else if params == [CsiParam::Integer(0)] { Ok(self.advance_by(1, params, Device::RequestSecondaryDeviceAttributes)) } else { Err(()) } } - fn secondary_device_attributes(&mut self, params: &'a [i64]) -> Result { - if params == [1, 0] { + fn secondary_device_attributes(&mut self, params: &'a [CsiParam]) -> Result { + if params == [CsiParam::Integer(1), CsiParam::Integer(0)] { Ok(self.advance_by( 2, params, Device::DeviceAttributes(DeviceAttributes::Vt101WithNoOptions), )) - } else if params == [6] { + } else if params == [CsiParam::Integer(6)] { 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( 2, params, 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( params.len(), params, @@ -1615,7 +1628,7 @@ impl<'a> CSIParser<'a> { DeviceAttributeFlags::from_params(¶ms[1..]), )), )) - } else if !params.is_empty() && params[0] == 63 { + } else if !params.is_empty() && params[0] == CsiParam::Integer(63) { Ok(self.advance_by( params.len(), params, @@ -1623,7 +1636,7 @@ impl<'a> CSIParser<'a> { DeviceAttributeFlags::from_params(¶ms[1..]), )), )) - } else if !params.is_empty() && params[0] == 64 { + } else if !params.is_empty() && params[0] == CsiParam::Integer(64) { Ok(self.advance_by( params.len(), params, @@ -1637,13 +1650,15 @@ impl<'a> CSIParser<'a> { } /// Parse extended mouse reports known as SGR 1006 mode - fn mouse_sgr1006(&mut self, params: &'a [i64]) -> Result { + fn mouse_sgr1006(&mut self, params: &'a [CsiParam]) -> Result { if params.len() != 3 { return Err(()); } + let p0 = params[0].as_integer().unwrap(); + // '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::Button1Release, ('M', 1) => MouseButton::Button2Press, @@ -1672,30 +1687,36 @@ impl<'a> CSIParser<'a> { }; let mut modifiers = Modifiers::NONE; - if params[0] & 4 != 0 { + if p0 & 4 != 0 { modifiers |= Modifiers::SHIFT; } - if params[0] & 8 != 0 { + if p0 & 8 != 0 { modifiers |= Modifiers::ALT; } - if params[0] & 16 != 0 { + if p0 & 16 != 0 { modifiers |= Modifiers::CTRL; } + let p1 = params[1].as_integer().unwrap(); + let p2 = params[2].as_integer().unwrap(); + Ok(self.advance_by( 3, params, MouseReport::SGR1006 { - x: params[1] as u16, - y: params[2] as u16, + x: p1 as u16, + y: p2 as u16, button, modifiers, }, )) } - fn dec(&mut self, params: &'a [i64]) -> Result { - let p0 = *params.get(0).ok_or_else(|| ())?; + fn dec(&mut self, params: &'a [CsiParam]) -> Result { + let p0 = params + .get(0) + .and_then(CsiParam::as_integer) + .ok_or_else(|| ())?; match FromPrimitive::from_i64(p0) { None => Ok(self.advance_by( 1, @@ -1706,8 +1727,11 @@ impl<'a> CSIParser<'a> { } } - fn terminal_mode(&mut self, params: &'a [i64]) -> Result { - let p0 = *params.get(0).ok_or_else(|| ())?; + fn terminal_mode(&mut self, params: &'a [CsiParam]) -> Result { + let p0 = params + .get(0) + .and_then(CsiParam::as_integer) + .ok_or_else(|| ())?; match FromPrimitive::from_i64(p0) { None => { 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 { - if params.len() >= 5 && params[1] == 2 { - let red = to_u8(params[2])?; - let green = to_u8(params[3])?; - let blue = to_u8(params[4])?; + fn parse_sgr_color(&mut self, params: &'a [CsiParam]) -> Result { + if params.len() >= 5 && params[1].as_integer() == Some(2) { + let red = to_u8(¶ms[2])?; + let green = to_u8(¶ms[3])?; + let blue = to_u8(¶ms[4])?; let res = RgbColor::new(red, green, blue).into(); Ok(self.advance_by(5, params, res)) - } else if params.len() >= 3 && params[1] == 5 { - let idx = to_u8(params[2])?; + } else if params.len() >= 3 && params[1].as_integer() == Some(5) { + let idx = to_u8(¶ms[2])?; Ok(self.advance_by(3, params, ColorSpec::PaletteIndex(idx))) } else { Err(()) } } - fn window(&mut self, params: &'a [i64]) -> Result { + fn window(&mut self, params: &'a [CsiParam]) -> Result { if params.is_empty() { Err(()) } else { - let arg1 = params.get(1).cloned(); - let arg2 = params.get(2).cloned(); - match params[0] { - 1 => Ok(Window::DeIconify), - 2 => Ok(Window::Iconify), - 3 => Ok(Window::MoveWindow { - x: arg1.unwrap_or(0), - y: arg2.unwrap_or(0), - }), - 4 => Ok(Window::ResizeWindowPixels { - height: arg1, - width: arg2, - }), - 5 => Ok(Window::RaiseWindow), - 6 => match params.len() { - 1 => Ok(Window::LowerWindow), - 3 => Ok(Window::ReportCellSizePixelsResponse { + let arg1 = params.get(1).and_then(CsiParam::as_integer); + let arg2 = params.get(2).and_then(CsiParam::as_integer); + match params[0].as_integer() { + None => Err(()), + Some(p) => match p { + 1 => Ok(Window::DeIconify), + 2 => Ok(Window::Iconify), + 3 => Ok(Window::MoveWindow { + x: arg1.unwrap_or(0), + y: arg2.unwrap_or(0), + }), + 4 => Ok(Window::ResizeWindowPixels { height: arg1, 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(()), }, - 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 { + fn sgr(&mut self, params: &'a [CsiParam]) -> Result { if params.is_empty() { // With no parameters, treat as equivalent to Reset. Ok(Sgr::Reset) @@ -1821,97 +1848,130 @@ impl<'a> CSIParser<'a> { }; }; - match FromPrimitive::from_i64(params[0]) { - None => Err(()), - Some(sgr) => match sgr { - SgrCode::Reset => one!(Sgr::Reset), - SgrCode::IntensityBold => one!(Sgr::Intensity(Intensity::Bold)), - SgrCode::IntensityDim => one!(Sgr::Intensity(Intensity::Half)), - SgrCode::NormalIntensity => one!(Sgr::Intensity(Intensity::Normal)), - SgrCode::UnderlineOn => one!(Sgr::Underline(Underline::Single)), - SgrCode::UnderlineDouble => one!(Sgr::Underline(Underline::Double)), - SgrCode::UnderlineCurly => one!(Sgr::Underline(Underline::Curly)), - SgrCode::UnderlineDotted => one!(Sgr::Underline(Underline::Dotted)), - SgrCode::UnderlineDashed => one!(Sgr::Underline(Underline::Dashed)), - SgrCode::UnderlineOff => one!(Sgr::Underline(Underline::None)), - 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::RapidBlinkOn => one!(Sgr::Blink(Blink::Rapid)), - SgrCode::BlinkOff => one!(Sgr::Blink(Blink::None)), - SgrCode::ItalicOn => one!(Sgr::Italic(true)), - SgrCode::ItalicOff => one!(Sgr::Italic(false)), - SgrCode::ForegroundColor => self.parse_sgr_color(params).map(Sgr::Foreground), - SgrCode::ForegroundBlack => one!(Sgr::Foreground(AnsiColor::Black.into())), - SgrCode::ForegroundRed => one!(Sgr::Foreground(AnsiColor::Maroon.into())), - SgrCode::ForegroundGreen => one!(Sgr::Foreground(AnsiColor::Green.into())), - SgrCode::ForegroundYellow => one!(Sgr::Foreground(AnsiColor::Olive.into())), - SgrCode::ForegroundBlue => one!(Sgr::Foreground(AnsiColor::Navy.into())), - SgrCode::ForegroundMagenta => one!(Sgr::Foreground(AnsiColor::Purple.into())), - SgrCode::ForegroundCyan => one!(Sgr::Foreground(AnsiColor::Teal.into())), - SgrCode::ForegroundWhite => one!(Sgr::Foreground(AnsiColor::Silver.into())), - SgrCode::ForegroundDefault => one!(Sgr::Foreground(ColorSpec::Default)), - SgrCode::ForegroundBrightBlack => one!(Sgr::Foreground(AnsiColor::Grey.into())), - SgrCode::ForegroundBrightRed => one!(Sgr::Foreground(AnsiColor::Red.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())) - } + match params[0] { + CsiParam::Integer(i) => match FromPrimitive::from_i64(i) { + None => Err(()), + Some(sgr) => match sgr { + SgrCode::Reset => one!(Sgr::Reset), + SgrCode::IntensityBold => one!(Sgr::Intensity(Intensity::Bold)), + SgrCode::IntensityDim => one!(Sgr::Intensity(Intensity::Half)), + SgrCode::NormalIntensity => one!(Sgr::Intensity(Intensity::Normal)), + SgrCode::UnderlineOn => one!(Sgr::Underline(Underline::Single)), + SgrCode::UnderlineDouble => one!(Sgr::Underline(Underline::Double)), + SgrCode::UnderlineCurly => one!(Sgr::Underline(Underline::Curly)), + SgrCode::UnderlineDotted => one!(Sgr::Underline(Underline::Dotted)), + SgrCode::UnderlineDashed => one!(Sgr::Underline(Underline::Dashed)), + SgrCode::UnderlineOff => one!(Sgr::Underline(Underline::None)), + 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::RapidBlinkOn => one!(Sgr::Blink(Blink::Rapid)), + SgrCode::BlinkOff => one!(Sgr::Blink(Blink::None)), + SgrCode::ItalicOn => one!(Sgr::Italic(true)), + SgrCode::ItalicOff => one!(Sgr::Italic(false)), + SgrCode::ForegroundColor => { + self.parse_sgr_color(params).map(Sgr::Foreground) + } + SgrCode::ForegroundBlack => one!(Sgr::Foreground(AnsiColor::Black.into())), + SgrCode::ForegroundRed => one!(Sgr::Foreground(AnsiColor::Maroon.into())), + SgrCode::ForegroundGreen => one!(Sgr::Foreground(AnsiColor::Green.into())), + SgrCode::ForegroundYellow => one!(Sgr::Foreground(AnsiColor::Olive.into())), + SgrCode::ForegroundBlue => one!(Sgr::Foreground(AnsiColor::Navy.into())), + SgrCode::ForegroundMagenta => { + one!(Sgr::Foreground(AnsiColor::Purple.into())) + } + SgrCode::ForegroundCyan => one!(Sgr::Foreground(AnsiColor::Teal.into())), + SgrCode::ForegroundWhite => one!(Sgr::Foreground(AnsiColor::Silver.into())), + SgrCode::ForegroundDefault => one!(Sgr::Foreground(ColorSpec::Default)), + SgrCode::ForegroundBrightBlack => { + one!(Sgr::Foreground(AnsiColor::Grey.into())) + } + SgrCode::ForegroundBrightRed => { + one!(Sgr::Foreground(AnsiColor::Red.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::BackgroundBlack => one!(Sgr::Background(AnsiColor::Black.into())), - SgrCode::BackgroundRed => one!(Sgr::Background(AnsiColor::Maroon.into())), - SgrCode::BackgroundGreen => one!(Sgr::Background(AnsiColor::Green.into())), - SgrCode::BackgroundYellow => one!(Sgr::Background(AnsiColor::Olive.into())), - SgrCode::BackgroundBlue => one!(Sgr::Background(AnsiColor::Navy.into())), - SgrCode::BackgroundMagenta => one!(Sgr::Background(AnsiColor::Purple.into())), - SgrCode::BackgroundCyan => one!(Sgr::Background(AnsiColor::Teal.into())), - SgrCode::BackgroundWhite => one!(Sgr::Background(AnsiColor::Silver.into())), - SgrCode::BackgroundDefault => one!(Sgr::Background(ColorSpec::Default)), - SgrCode::BackgroundBrightBlack => one!(Sgr::Background(AnsiColor::Grey.into())), - SgrCode::BackgroundBrightRed => one!(Sgr::Background(AnsiColor::Red.into())), - SgrCode::BackgroundBrightGreen => one!(Sgr::Background(AnsiColor::Lime.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::BackgroundColor => { + self.parse_sgr_color(params).map(Sgr::Background) + } + SgrCode::BackgroundBlack => one!(Sgr::Background(AnsiColor::Black.into())), + SgrCode::BackgroundRed => one!(Sgr::Background(AnsiColor::Maroon.into())), + SgrCode::BackgroundGreen => one!(Sgr::Background(AnsiColor::Green.into())), + SgrCode::BackgroundYellow => one!(Sgr::Background(AnsiColor::Olive.into())), + SgrCode::BackgroundBlue => one!(Sgr::Background(AnsiColor::Navy.into())), + SgrCode::BackgroundMagenta => { + one!(Sgr::Background(AnsiColor::Purple.into())) + } + SgrCode::BackgroundCyan => one!(Sgr::Background(AnsiColor::Teal.into())), + SgrCode::BackgroundWhite => one!(Sgr::Background(AnsiColor::Silver.into())), + SgrCode::BackgroundDefault => one!(Sgr::Background(ColorSpec::Default)), + SgrCode::BackgroundBrightBlack => { + one!(Sgr::Background(AnsiColor::Grey.into())) + } + SgrCode::BackgroundBrightRed => { + one!(Sgr::Background(AnsiColor::Red.into())) + } + SgrCode::BackgroundBrightGreen => { + one!(Sgr::Background(AnsiColor::Lime.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::InverseOff => one!(Sgr::Inverse(false)), - SgrCode::InvisibleOn => one!(Sgr::Invisible(true)), - SgrCode::InvisibleOff => one!(Sgr::Invisible(false)), - SgrCode::StrikeThroughOn => one!(Sgr::StrikeThrough(true)), - SgrCode::StrikeThroughOff => one!(Sgr::StrikeThrough(false)), - SgrCode::OverlineOn => one!(Sgr::Overline(true)), - SgrCode::OverlineOff => one!(Sgr::Overline(false)), - SgrCode::DefaultFont => one!(Sgr::Font(Font::Default)), - SgrCode::AltFont1 => one!(Sgr::Font(Font::Alternate(1))), - SgrCode::AltFont2 => one!(Sgr::Font(Font::Alternate(2))), - SgrCode::AltFont3 => one!(Sgr::Font(Font::Alternate(3))), - SgrCode::AltFont4 => one!(Sgr::Font(Font::Alternate(4))), - SgrCode::AltFont5 => one!(Sgr::Font(Font::Alternate(5))), - SgrCode::AltFont6 => one!(Sgr::Font(Font::Alternate(6))), - SgrCode::AltFont7 => one!(Sgr::Font(Font::Alternate(7))), - SgrCode::AltFont8 => one!(Sgr::Font(Font::Alternate(8))), - SgrCode::AltFont9 => one!(Sgr::Font(Font::Alternate(9))), + SgrCode::InverseOn => one!(Sgr::Inverse(true)), + SgrCode::InverseOff => one!(Sgr::Inverse(false)), + SgrCode::InvisibleOn => one!(Sgr::Invisible(true)), + SgrCode::InvisibleOff => one!(Sgr::Invisible(false)), + SgrCode::StrikeThroughOn => one!(Sgr::StrikeThrough(true)), + SgrCode::StrikeThroughOff => one!(Sgr::StrikeThrough(false)), + SgrCode::OverlineOn => one!(Sgr::Overline(true)), + SgrCode::OverlineOff => one!(Sgr::Overline(false)), + SgrCode::DefaultFont => one!(Sgr::Font(Font::Default)), + SgrCode::AltFont1 => one!(Sgr::Font(Font::Alternate(1))), + SgrCode::AltFont2 => one!(Sgr::Font(Font::Alternate(2))), + SgrCode::AltFont3 => one!(Sgr::Font(Font::Alternate(3))), + SgrCode::AltFont4 => one!(Sgr::Font(Font::Alternate(4))), + SgrCode::AltFont5 => one!(Sgr::Font(Font::Alternate(5))), + SgrCode::AltFont6 => one!(Sgr::Font(Font::Alternate(6))), + SgrCode::AltFont7 => one!(Sgr::Font(Font::Alternate(7))), + SgrCode::AltFont8 => one!(Sgr::Font(Font::Alternate(8))), + SgrCode::AltFont9 => one!(Sgr::Font(Font::Alternate(9))), + }, }, + CsiParam::ColonList(_) => Err(()), } } } @@ -2028,14 +2088,22 @@ mod test { use std::io::Write; fn parse(control: char, params: &[i64], expected: &str) -> Vec { - let res = CSI::parse(params, &[], false, control).collect(); + let params = params + .iter() + .map(|&i| CsiParam::Integer(i)) + .collect::>(); + let res = CSI::parse(¶ms, &[], false, control).collect(); assert_eq!(encode(&res), expected); res } fn parse_int(control: char, params: &[i64], intermediate: u8, expected: &str) -> Vec { + let params = params + .iter() + .map(|&i| CsiParam::Integer(i)) + .collect::>(); let intermediates = [intermediate]; - let res = CSI::parse(params, &intermediates, false, control).collect(); + let res = CSI::parse(¶ms, &intermediates, false, control).collect(); assert_eq!(encode(&res), expected); res } @@ -2072,7 +2140,7 @@ mod test { CSI::Sgr(Sgr::Intensity(Intensity::Bold)), CSI::Sgr(Sgr::Italic(true)), CSI::Unspecified(Box::new(Unspecified { - params: [1231231].to_vec(), + params: [CsiParam::Integer(1231231)].to_vec(), intermediates: vec![], ignored_extra_intermediates: false, control: 'm', @@ -2084,7 +2152,7 @@ mod test { vec![ CSI::Sgr(Sgr::Intensity(Intensity::Bold)), CSI::Unspecified(Box::new(Unspecified { - params: [1231231, 3].to_vec(), + params: [CsiParam::Integer(1231231), CsiParam::Integer(3)].to_vec(), intermediates: vec![], ignored_extra_intermediates: false, control: 'm', @@ -2094,7 +2162,7 @@ mod test { assert_eq!( parse('m', &[1231231, 3], "\x1b[1231231;3m"), vec![CSI::Unspecified(Box::new(Unspecified { - params: [1231231, 3].to_vec(), + params: [CsiParam::Integer(1231231), CsiParam::Integer(3)].to_vec(), intermediates: vec![], ignored_extra_intermediates: false, control: 'm', @@ -2119,7 +2187,7 @@ mod test { assert_eq!( parse('m', &[58, 2], "\x1b[58;2m"), vec![CSI::Unspecified(Box::new(Unspecified { - params: [58, 2].to_vec(), + params: [CsiParam::Integer(58), CsiParam::Integer(2)].to_vec(), intermediates: vec![], ignored_extra_intermediates: false, control: 'm', @@ -2137,7 +2205,7 @@ mod test { vec![ CSI::Sgr(Sgr::UnderlineColor(ColorSpec::PaletteIndex(220))), CSI::Unspecified(Box::new(Unspecified { - params: [255, 255].to_vec(), + params: [CsiParam::Integer(255), CsiParam::Integer(255)].to_vec(), intermediates: vec![], ignored_extra_intermediates: false, control: 'm', @@ -2151,7 +2219,7 @@ mod test { assert_eq!( parse('m', &[38, 2], "\x1b[38;2m"), vec![CSI::Unspecified(Box::new(Unspecified { - params: [38, 2].to_vec(), + params: [CsiParam::Integer(38), CsiParam::Integer(2)].to_vec(), intermediates: vec![], ignored_extra_intermediates: false, control: 'm', @@ -2169,7 +2237,7 @@ mod test { vec![ CSI::Sgr(Sgr::Foreground(ColorSpec::PaletteIndex(220))), CSI::Unspecified(Box::new(Unspecified { - params: [255, 255].to_vec(), + params: [CsiParam::Integer(255), CsiParam::Integer(255)].to_vec(), intermediates: vec![], ignored_extra_intermediates: false, control: 'm', diff --git a/termwiz/src/escape/mod.rs b/termwiz/src/escape/mod.rs index 074d5312c..b1255f9da 100644 --- a/termwiz/src/escape/mod.rs +++ b/termwiz/src/escape/mod.rs @@ -18,6 +18,8 @@ pub use self::esc::Esc; pub use self::esc::EscCode; pub use self::osc::OperatingSystemCommand; +use vtparse::CsiParam; + #[derive(Debug, Clone, PartialEq, Eq)] pub enum Action { /// Send a single printable character to the display @@ -477,35 +479,35 @@ impl OneBased { /// Map a value from an escape sequence parameter. /// 0 is equivalent to 1 - pub fn from_esc_param(v: i64) -> Result { - if v == 0 { - Ok(Self { + pub fn from_esc_param(v: &CsiParam) -> Result { + match v { + CsiParam::Integer(v) if *v == 0 => Ok(Self { value: num_traits::one(), - }) - } else if v > 0 && v <= i64::from(u32::max_value()) { - Ok(Self { value: v as u32 }) - } else { - Err(()) + }), + CsiParam::Integer(v) if *v > 0 && *v <= i64::from(u32::max_value()) => { + Ok(Self { value: *v as u32 }) + } + _ => Err(()), } } /// Map a value from an escape sequence parameter. /// 0 is equivalent to max_value. - pub fn from_esc_param_with_big_default(v: i64) -> Result { - if v == 0 { - Ok(Self { + pub fn from_esc_param_with_big_default(v: &CsiParam) -> Result { + match v { + CsiParam::Integer(v) if *v == 0 => Ok(Self { value: u32::max_value(), - }) - } else if v > 0 && v <= i64::from(u32::max_value()) { - Ok(Self { value: v as u32 }) - } else { - Err(()) + }), + CsiParam::Integer(v) if *v > 0 && *v <= i64::from(u32::max_value()) => { + Ok(Self { value: *v as u32 }) + } + _ => Err(()), } } /// Map a value from an optional escape sequence parameter - pub fn from_optional_esc_param(o: Option<&i64>) -> Result { - Self::from_esc_param(o.cloned().unwrap_or(1)) + pub fn from_optional_esc_param(o: Option<&CsiParam>) -> Result { + Self::from_esc_param(o.unwrap_or(&CsiParam::Integer(1))) } /// Return the underlying value as a 0-based value diff --git a/termwiz/src/escape/parser/mod.rs b/termwiz/src/escape/parser/mod.rs index e0236a220..1cbc3246a 100644 --- a/termwiz/src/escape/parser/mod.rs +++ b/termwiz/src/escape/parser/mod.rs @@ -7,7 +7,7 @@ use log::error; use num_traits::FromPrimitive; use regex::bytes::Regex; use std::cell::RefCell; -use vtparse::{VTActor, VTParser}; +use vtparse::{CsiParam, VTActor, VTParser}; struct SixelBuilder { sixel: Sixel, @@ -213,7 +213,7 @@ impl<'a, F: FnMut(Action)> VTActor for Performer<'a, F> { fn csi_dispatch( &mut self, - params: &[i64], + params: &[CsiParam], intermediates: &[u8], ignored_extra_intermediates: bool, control: u8, @@ -482,12 +482,17 @@ mod test { fn fancy_underline() { 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"); assert_eq!( 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'), ], actions diff --git a/vtparse/Cargo.toml b/vtparse/Cargo.toml index 1b0009070..18b0ff225 100644 --- a/vtparse/Cargo.toml +++ b/vtparse/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["Wez Furlong "] name = "vtparse" -version = "0.3.0" +version = "0.4.0" edition = "2018" repository = "https://github.com/wez/wezterm" description = "Low level escape sequence parser" diff --git a/vtparse/src/lib.rs b/vtparse/src/lib.rs index 07b3793f5..817a6b57a 100644 --- a/vtparse/src/lib.rs +++ b/vtparse/src/lib.rs @@ -151,7 +151,7 @@ pub trait VTActor { /// for more information on control functions. fn csi_dispatch( &mut self, - params: &[i64], + params: &[CsiParam], intermediates: &[u8], ignored_excess_intermediates: bool, byte: u8, @@ -187,7 +187,7 @@ pub enum VTAction { byte: u8, }, CsiDispatch { - params: Vec, + params: Vec, intermediates: Vec, ignored_excess_intermediates: bool, byte: u8, @@ -268,7 +268,7 @@ impl VTActor for CollectingVTActor { fn csi_dispatch( &mut self, - params: &[i64], + params: &[CsiParam], intermediates: &[u8], ignored_excess_intermediates: bool, byte: u8, @@ -333,20 +333,84 @@ pub struct VTParser { osc: OscState, - params: [i64; MAX_PARAMS], + params: [CsiParam; MAX_PARAMS], num_params: usize, - current_param: Option, + current_param: Option, params_full: bool, utf8_parser: Utf8Parser, utf8_return_state: State, } +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +pub enum CsiParam { + Integer(i64), + ColonList(Vec>), + // 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 { + 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 { #[allow(clippy::new_without_default)] pub fn new() -> Self { let param_indices = [0usize; MAX_OSC]; - let params = [0i64; MAX_PARAMS]; Self { state: State::Ground, @@ -363,7 +427,7 @@ impl VTParser { full: false, }, - params, + params: Default::default(), num_params: 0, params_full: false, 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) { if let Some(val) = self.current_param.take() { if self.num_params < MAX_PARAMS { @@ -411,24 +489,34 @@ impl VTParser { if self.num_params + 1 > MAX_OSC { self.params_full = true; } 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; } - } else { - let current = self.current_param.take().unwrap_or(0); + } else if param == b':' { + 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 - .saturating_mul(10) - .saturating_add((param - b'0') as i64), - ); + current.push(None); // Start a new, empty parameter + + self.current_param.replace(CsiParam::ColonList(current)); + } else { + let mut current = self.current_param.take().unwrap_or(CsiParam::Integer(0)); + current.add_digit(param); + self.current_param.replace(current); } } Action::Hook => { self.finish_param(); actor.dcs_hook( param, - &self.params[0..self.num_params], + &self.as_integer_params()[0..self.num_params], &self.intermediates[0..self.num_intermediates], self.ignored_excess_intermediates, ); @@ -437,7 +525,7 @@ impl VTParser { Action::EscDispatch => { self.finish_param(); actor.esc_dispatch( - &self.params[0..self.num_params], + &self.as_integer_params()[0..self.num_params], &self.intermediates[0..self.num_intermediates], self.ignored_excess_intermediates, param, @@ -599,7 +687,7 @@ mod test { VTAction::Print('o'), VTAction::ExecuteC0orC1(0x07,), VTAction::CsiDispatch { - params: vec![32], + params: vec![CsiParam::Integer(32)], intermediates: vec![], ignored_excess_intermediates: false, byte: b'm', @@ -609,7 +697,7 @@ mod test { VTAction::Print('o',), VTAction::Print('t',), VTAction::CsiDispatch { - params: vec![0], + params: vec![CsiParam::Integer(0)], intermediates: vec![], ignored_excess_intermediates: false, byte: b'm', @@ -712,7 +800,7 @@ mod test { assert_eq!( parse_as_vec(b"\x1b[4m"), vec![VTAction::CsiDispatch { - params: vec![4], + params: vec![CsiParam::Integer(4)], intermediates: b"".to_vec(), ignored_excess_intermediates: false, byte: b'm' @@ -721,11 +809,33 @@ mod test { assert_eq!( // 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"), - 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"), vec![VTAction::CsiDispatch { // The omitted parameter defaults to 0 - params: vec![0, 1], + params: vec![CsiParam::Integer(0), CsiParam::Integer(1)], intermediates: b"".to_vec(), ignored_excess_intermediates: false, byte: b'm' @@ -748,7 +858,10 @@ mod test { assert_eq!( 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 { - 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(), ignored_excess_intermediates: false, byte: b'p' @@ -761,7 +874,7 @@ mod test { assert_eq!( parse_as_vec(b"\x1b[1 p"), vec![VTAction::CsiDispatch { - params: vec![1], + params: vec![CsiParam::Integer(1)], intermediates: b" ".to_vec(), ignored_excess_intermediates: false, byte: b'p' @@ -770,7 +883,7 @@ mod test { assert_eq!( parse_as_vec(b"\x1b[1 !p"), vec![VTAction::CsiDispatch { - params: vec![1], + params: vec![CsiParam::Integer(1)], intermediates: b" !".to_vec(), ignored_excess_intermediates: false, byte: b'p' @@ -779,7 +892,7 @@ mod test { assert_eq!( parse_as_vec(b"\x1b[1 !#p"), vec![VTAction::CsiDispatch { - params: vec![1], + params: vec![CsiParam::Integer(1)], // Note that the `#` was discarded intermediates: b" !".to_vec(), ignored_excess_intermediates: true, diff --git a/vtparse/src/transitions.rs b/vtparse/src/transitions.rs index f1a918f18..633e38b0d 100644 --- a/vtparse/src/transitions.rs +++ b/vtparse/src/transitions.rs @@ -194,10 +194,8 @@ define_function! { 0x00..=0x17 => (Execute, CsiParam), 0x19 => (Execute, CsiParam), 0x1c..=0x1f => (Execute, CsiParam), - 0x30..=0x39 => (Param, CsiParam), - 0x3b => (Param, CsiParam), + 0x30..=0x3b => (Param, CsiParam), 0x7f => (Ignore, CsiParam), - 0x3a => (None, CsiIgnore), 0x3c..=0x3f => (None, CsiIgnore), 0x20..=0x2f => (Collect, CsiIntermediate), 0x40..=0x7e => (CsiDispatch, Ground),