mirror of
https://github.com/wez/wezterm.git
synced 2024-12-23 13:21:38 +03:00
WIP: improve CSI parsing fidelity
The original design of the vtparse crate was inspired by the vte crate. There were some assumptions about the shape of CSI sequences that were lossy and that is posing a problem when it comes to implementing DECRQM. This commit improves the situation by adjusting CsiParam to be capable of capturing all of the possible parameters as well as intermediates. This commit isn't done; I just need to push it to transfer it to another machine. refs: https://github.com/wez/wezterm/issues/882 refs: https://github.com/wez/wezterm/issues/955
This commit is contained in:
parent
c6dc677e50
commit
86c1d50b4a
@ -48,16 +48,12 @@ pub struct Unspecified {
|
||||
|
||||
impl Display for Unspecified {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
|
||||
for p in &self.params {
|
||||
write!(f, "{}", p)?;
|
||||
}
|
||||
for i in &self.intermediates {
|
||||
write!(f, "{}", *i as char)?;
|
||||
}
|
||||
for (idx, p) in self.params.iter().enumerate() {
|
||||
if idx > 0 {
|
||||
write!(f, ";{}", p)?;
|
||||
} else {
|
||||
write!(f, "{}", p)?;
|
||||
}
|
||||
}
|
||||
write!(f, "{}", self.control)
|
||||
}
|
||||
}
|
||||
@ -152,6 +148,7 @@ impl DeviceAttributeFlags {
|
||||
Some(c) => attributes.push(DeviceAttribute::Code(c)),
|
||||
None => attributes.push(DeviceAttribute::Unspecified(i.clone())),
|
||||
},
|
||||
CsiParam::P(b';') => {}
|
||||
_ => attributes.push(DeviceAttribute::Unspecified(i.clone())),
|
||||
}
|
||||
}
|
||||
@ -1077,12 +1074,10 @@ trait ParseParams: Sized {
|
||||
/// Parse an input parameter into a 1-based unsigned value
|
||||
impl ParseParams for u32 {
|
||||
fn parse_params(params: &[CsiParam]) -> Result<u32, ()> {
|
||||
if params.is_empty() {
|
||||
Ok(1)
|
||||
} else if params.len() == 1 {
|
||||
to_1b_u32(¶ms[0])
|
||||
} else {
|
||||
Err(())
|
||||
match params {
|
||||
[] => Ok(1),
|
||||
[p] => to_1b_u32(p),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1090,12 +1085,10 @@ impl ParseParams for u32 {
|
||||
/// Parse an input parameter into a 1-based unsigned value
|
||||
impl ParseParams for OneBased {
|
||||
fn parse_params(params: &[CsiParam]) -> Result<OneBased, ()> {
|
||||
if params.is_empty() {
|
||||
Ok(OneBased::new(1))
|
||||
} else if params.len() == 1 {
|
||||
OneBased::from_esc_param(¶ms[0])
|
||||
} else {
|
||||
Err(())
|
||||
match params {
|
||||
[] => Ok(OneBased::new(1)),
|
||||
[p] => OneBased::from_esc_param(p),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1105,17 +1098,13 @@ impl ParseParams for OneBased {
|
||||
/// the pair of values.
|
||||
impl ParseParams for (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(¶ms[0])?, OneBased::new(1)))
|
||||
} else if params.len() == 2 {
|
||||
Ok((
|
||||
OneBased::from_esc_param(¶ms[0])?,
|
||||
OneBased::from_esc_param(¶ms[1])?,
|
||||
))
|
||||
} else {
|
||||
Err(())
|
||||
match params {
|
||||
[] => Ok((OneBased::new(1), OneBased::new(1))),
|
||||
[p] => Ok((OneBased::from_esc_param(p)?, OneBased::new(1))),
|
||||
[a, CsiParam::P(b';'), b] => {
|
||||
Ok((OneBased::from_esc_param(a)?, OneBased::from_esc_param(b)?))
|
||||
}
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1131,15 +1120,10 @@ trait ParamEnum: FromPrimitive {
|
||||
/// implement ParseParams for the enums that also implement ParamEnum.
|
||||
impl<T: ParamEnum> ParseParams for T {
|
||||
fn parse_params(params: &[CsiParam]) -> Result<Self, ()> {
|
||||
if params.is_empty() {
|
||||
Ok(ParamEnum::default())
|
||||
} else if params.len() == 1 {
|
||||
match params[0] {
|
||||
CsiParam::Integer(i) => FromPrimitive::from_i64(i).ok_or(()),
|
||||
CsiParam::ColonList(_) => Err(()),
|
||||
}
|
||||
} else {
|
||||
Err(())
|
||||
match params {
|
||||
[] => Ok(ParamEnum::default()),
|
||||
[CsiParam::Integer(i)] => FromPrimitive::from_i64(*i).ok_or(()),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1427,7 +1411,7 @@ impl CSI {
|
||||
/// A little helper to convert i64 -> u8 if safe
|
||||
fn to_u8(v: &CsiParam) -> Result<u8, ()> {
|
||||
match v {
|
||||
CsiParam::ColonList(_) => Err(()),
|
||||
CsiParam::P(_) => Err(()),
|
||||
CsiParam::Integer(v) => {
|
||||
if *v <= i64::from(u8::max_value()) {
|
||||
Ok(*v as u8)
|
||||
@ -1485,6 +1469,13 @@ macro_rules! parse {
|
||||
impl<'a> CSIParser<'a> {
|
||||
fn parse_next(&mut self, params: &'a [CsiParam]) -> Result<CSI, ()> {
|
||||
match (self.control, self.intermediates) {
|
||||
('m', &[]) | ('M', &[]) if params.get(0) == Some(&CsiParam::P(b'<')) => {
|
||||
self.mouse_sgr1006(params).map(CSI::Mouse)
|
||||
}
|
||||
('c', &[]) if params.get(0) == Some(&CsiParam::P(b'?')) => self
|
||||
.secondary_device_attributes(params)
|
||||
.map(|dev| CSI::Device(Box::new(dev))),
|
||||
|
||||
('@', &[]) => parse!(Edit, InsertCharacter, params),
|
||||
('`', &[]) => parse!(Cursor, CharacterPositionAbsolute, params),
|
||||
('A', &[]) => parse!(Cursor, Up, params),
|
||||
@ -1570,7 +1561,6 @@ impl<'a> CSIParser<'a> {
|
||||
.dec(params)
|
||||
.map(|mode| CSI::Mode(Mode::SaveDecPrivateMode(mode))),
|
||||
|
||||
('m', &[b'<']) | ('M', &[b'<']) => self.mouse_sgr1006(params).map(CSI::Mouse),
|
||||
('m', &[b'>']) => self.xterm_key_modifier(params),
|
||||
|
||||
('c', &[]) => self
|
||||
@ -1579,9 +1569,6 @@ impl<'a> CSIParser<'a> {
|
||||
('c', &[b'>']) => self
|
||||
.req_secondary_device_attributes(params)
|
||||
.map(|dev| CSI::Device(Box::new(dev))),
|
||||
('c', &[b'?']) => self
|
||||
.secondary_device_attributes(params)
|
||||
.map(|dev| CSI::Device(Box::new(dev))),
|
||||
('c', &[b'=']) => self
|
||||
.req_tertiary_device_attributes(params)
|
||||
.map(|dev| CSI::Device(Box::new(dev))),
|
||||
@ -1595,6 +1582,12 @@ impl<'a> CSIParser<'a> {
|
||||
/// as this would trigger returning a default value and/or
|
||||
/// an unterminated parse loop.
|
||||
fn advance_by<T>(&mut self, n: usize, params: &'a [CsiParam], result: T) -> T {
|
||||
let n = if matches!(params.get(n), Some(CsiParam::P(b';'))) {
|
||||
n + 1
|
||||
} else {
|
||||
n
|
||||
};
|
||||
|
||||
let (_, next) = params.split_at(n);
|
||||
if !next.is_empty() {
|
||||
self.params = Some(next);
|
||||
@ -1616,194 +1609,195 @@ impl<'a> CSIParser<'a> {
|
||||
}
|
||||
|
||||
fn dsr(&mut self, params: &'a [CsiParam]) -> Result<CSI, ()> {
|
||||
if params == [CsiParam::Integer(5)] {
|
||||
Ok(self.advance_by(1, params, CSI::Device(Box::new(Device::StatusReport))))
|
||||
} else if params == [CsiParam::Integer(6)] {
|
||||
Ok(self.advance_by(1, params, CSI::Cursor(Cursor::RequestActivePositionReport)))
|
||||
} else {
|
||||
Err(())
|
||||
match params {
|
||||
[CsiParam::Integer(5)] => {
|
||||
Ok(self.advance_by(1, params, CSI::Device(Box::new(Device::StatusReport))))
|
||||
}
|
||||
|
||||
[CsiParam::Integer(6)] => {
|
||||
Ok(self.advance_by(1, params, CSI::Cursor(Cursor::RequestActivePositionReport)))
|
||||
}
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn decstbm(&mut self, params: &'a [CsiParam]) -> Result<CSI, ()> {
|
||||
if params.is_empty() {
|
||||
Ok(CSI::Cursor(Cursor::SetTopAndBottomMargins {
|
||||
match params {
|
||||
[] => Ok(CSI::Cursor(Cursor::SetTopAndBottomMargins {
|
||||
top: OneBased::new(1),
|
||||
bottom: OneBased::new(u32::max_value()),
|
||||
}))
|
||||
} else if params.len() == 1 {
|
||||
Ok(self.advance_by(
|
||||
})),
|
||||
[p] => Ok(self.advance_by(
|
||||
1,
|
||||
params,
|
||||
CSI::Cursor(Cursor::SetTopAndBottomMargins {
|
||||
top: OneBased::from_esc_param(¶ms[0])?,
|
||||
top: OneBased::from_esc_param(p)?,
|
||||
bottom: OneBased::new(u32::max_value()),
|
||||
}),
|
||||
))
|
||||
} else if params.len() == 2 {
|
||||
Ok(self.advance_by(
|
||||
2,
|
||||
)),
|
||||
[a, CsiParam::P(b';'), b] => Ok(self.advance_by(
|
||||
3,
|
||||
params,
|
||||
CSI::Cursor(Cursor::SetTopAndBottomMargins {
|
||||
top: OneBased::from_esc_param(¶ms[0])?,
|
||||
bottom: OneBased::from_esc_param_with_big_default(¶ms[1])?,
|
||||
top: OneBased::from_esc_param(a)?,
|
||||
bottom: OneBased::from_esc_param_with_big_default(b)?,
|
||||
}),
|
||||
))
|
||||
} else {
|
||||
Err(())
|
||||
)),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn xterm_key_modifier(&mut self, params: &'a [CsiParam]) -> Result<CSI, ()> {
|
||||
if params.len() == 2 {
|
||||
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].as_integer().ok_or_else(|| ())?),
|
||||
}),
|
||||
))
|
||||
} else if params.len() == 1 {
|
||||
let resource = XtermKeyModifierResource::parse(params[0].as_integer().unwrap())
|
||||
.ok_or_else(|| ())?;
|
||||
Ok(self.advance_by(
|
||||
1,
|
||||
params,
|
||||
CSI::Mode(Mode::XtermKeyMode {
|
||||
resource,
|
||||
value: None,
|
||||
}),
|
||||
))
|
||||
} else {
|
||||
Err(())
|
||||
match params {
|
||||
[a, CsiParam::P(b';'), b] => {
|
||||
let resource =
|
||||
XtermKeyModifierResource::parse(a.as_integer().unwrap()).ok_or_else(|| ())?;
|
||||
Ok(self.advance_by(
|
||||
3,
|
||||
params,
|
||||
CSI::Mode(Mode::XtermKeyMode {
|
||||
resource,
|
||||
value: Some(b.as_integer().ok_or_else(|| ())?),
|
||||
}),
|
||||
))
|
||||
}
|
||||
[p] => {
|
||||
let resource =
|
||||
XtermKeyModifierResource::parse(p.as_integer().unwrap()).ok_or_else(|| ())?;
|
||||
Ok(self.advance_by(
|
||||
1,
|
||||
params,
|
||||
CSI::Mode(Mode::XtermKeyMode {
|
||||
resource,
|
||||
value: None,
|
||||
}),
|
||||
))
|
||||
}
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn decslrm(&mut self, params: &'a [CsiParam]) -> Result<CSI, ()> {
|
||||
if params.is_empty() {
|
||||
// with no params this is a request to save the cursor
|
||||
// and is technically in conflict with SetLeftAndRightMargins.
|
||||
// The emulator needs to decide based on DECSLRM mode
|
||||
// whether this saves the cursor or is SetLeftAndRightMargins
|
||||
// with default parameters!
|
||||
Ok(CSI::Cursor(Cursor::SaveCursor))
|
||||
} else if params.len() == 1 {
|
||||
Ok(self.advance_by(
|
||||
match params {
|
||||
[] => {
|
||||
// with no params this is a request to save the cursor
|
||||
// and is technically in conflict with SetLeftAndRightMargins.
|
||||
// The emulator needs to decide based on DECSLRM mode
|
||||
// whether this saves the cursor or is SetLeftAndRightMargins
|
||||
// with default parameters!
|
||||
Ok(CSI::Cursor(Cursor::SaveCursor))
|
||||
}
|
||||
[p] => Ok(self.advance_by(
|
||||
1,
|
||||
params,
|
||||
CSI::Cursor(Cursor::SetLeftAndRightMargins {
|
||||
left: OneBased::from_esc_param(¶ms[0])?,
|
||||
left: OneBased::from_esc_param(p)?,
|
||||
right: OneBased::new(u32::max_value()),
|
||||
}),
|
||||
))
|
||||
} else if params.len() == 2 {
|
||||
Ok(self.advance_by(
|
||||
2,
|
||||
)),
|
||||
[a, CsiParam::P(b';'), b] => Ok(self.advance_by(
|
||||
3,
|
||||
params,
|
||||
CSI::Cursor(Cursor::SetLeftAndRightMargins {
|
||||
left: OneBased::from_esc_param(¶ms[0])?,
|
||||
right: OneBased::from_esc_param(¶ms[1])?,
|
||||
left: OneBased::from_esc_param(a)?,
|
||||
right: OneBased::from_esc_param(b)?,
|
||||
}),
|
||||
))
|
||||
} else {
|
||||
Err(())
|
||||
)),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn req_primary_device_attributes(&mut self, params: &'a [CsiParam]) -> Result<Device, ()> {
|
||||
if params == [] {
|
||||
Ok(Device::RequestPrimaryDeviceAttributes)
|
||||
} else if params == [CsiParam::Integer(0)] {
|
||||
Ok(self.advance_by(1, params, Device::RequestPrimaryDeviceAttributes))
|
||||
} else {
|
||||
Err(())
|
||||
match params {
|
||||
[] => Ok(Device::RequestPrimaryDeviceAttributes),
|
||||
[CsiParam::Integer(0)] => {
|
||||
Ok(self.advance_by(1, params, Device::RequestPrimaryDeviceAttributes))
|
||||
}
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn req_terminal_name_and_version(&mut self, params: &'a [CsiParam]) -> Result<Device, ()> {
|
||||
if params == [] {
|
||||
Ok(Device::RequestTerminalNameAndVersion)
|
||||
} else if params == [CsiParam::Integer(0)] {
|
||||
Ok(self.advance_by(1, params, Device::RequestTerminalNameAndVersion))
|
||||
} else {
|
||||
Err(())
|
||||
match params {
|
||||
[] => Ok(Device::RequestTerminalNameAndVersion),
|
||||
|
||||
[CsiParam::Integer(0)] => {
|
||||
Ok(self.advance_by(1, params, Device::RequestTerminalNameAndVersion))
|
||||
}
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn req_secondary_device_attributes(&mut self, params: &'a [CsiParam]) -> Result<Device, ()> {
|
||||
if params == [] {
|
||||
Ok(Device::RequestSecondaryDeviceAttributes)
|
||||
} else if params == [CsiParam::Integer(0)] {
|
||||
Ok(self.advance_by(1, params, Device::RequestSecondaryDeviceAttributes))
|
||||
} else {
|
||||
Err(())
|
||||
match params {
|
||||
[] => Ok(Device::RequestSecondaryDeviceAttributes),
|
||||
[CsiParam::Integer(0)] => {
|
||||
Ok(self.advance_by(1, params, Device::RequestSecondaryDeviceAttributes))
|
||||
}
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn req_tertiary_device_attributes(&mut self, params: &'a [CsiParam]) -> Result<Device, ()> {
|
||||
if params == [] {
|
||||
Ok(Device::RequestTertiaryDeviceAttributes)
|
||||
} else if params == [CsiParam::Integer(0)] {
|
||||
Ok(self.advance_by(1, params, Device::RequestTertiaryDeviceAttributes))
|
||||
} else {
|
||||
Err(())
|
||||
match params {
|
||||
[] => Ok(Device::RequestTertiaryDeviceAttributes),
|
||||
[CsiParam::Integer(0)] => {
|
||||
Ok(self.advance_by(1, params, Device::RequestTertiaryDeviceAttributes))
|
||||
}
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn secondary_device_attributes(&mut self, params: &'a [CsiParam]) -> Result<Device, ()> {
|
||||
if params == [CsiParam::Integer(1), CsiParam::Integer(0)] {
|
||||
Ok(self.advance_by(
|
||||
2,
|
||||
params,
|
||||
Device::DeviceAttributes(DeviceAttributes::Vt101WithNoOptions),
|
||||
))
|
||||
} else if params == [CsiParam::Integer(6)] {
|
||||
Ok(self.advance_by(1, params, Device::DeviceAttributes(DeviceAttributes::Vt102)))
|
||||
} 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] == CsiParam::Integer(62) {
|
||||
Ok(self.advance_by(
|
||||
match params {
|
||||
[_, CsiParam::Integer(1), CsiParam::P(b';'), CsiParam::Integer(0)] => Ok(self
|
||||
.advance_by(
|
||||
4,
|
||||
params,
|
||||
Device::DeviceAttributes(DeviceAttributes::Vt101WithNoOptions),
|
||||
)),
|
||||
[_, CsiParam::Integer(6)] => {
|
||||
Ok(self.advance_by(2, params, Device::DeviceAttributes(DeviceAttributes::Vt102)))
|
||||
}
|
||||
[_, CsiParam::Integer(1), CsiParam::P(b';'), CsiParam::Integer(2)] => Ok(self
|
||||
.advance_by(
|
||||
3,
|
||||
params,
|
||||
Device::DeviceAttributes(DeviceAttributes::Vt100WithAdvancedVideoOption),
|
||||
)),
|
||||
[_, CsiParam::Integer(62), ..] => Ok(self.advance_by(
|
||||
params.len(),
|
||||
params,
|
||||
Device::DeviceAttributes(DeviceAttributes::Vt220(
|
||||
DeviceAttributeFlags::from_params(¶ms[1..]),
|
||||
DeviceAttributeFlags::from_params(¶ms[2..]),
|
||||
)),
|
||||
))
|
||||
} else if !params.is_empty() && params[0] == CsiParam::Integer(63) {
|
||||
Ok(self.advance_by(
|
||||
)),
|
||||
[_, CsiParam::Integer(63), ..] => Ok(self.advance_by(
|
||||
params.len(),
|
||||
params,
|
||||
Device::DeviceAttributes(DeviceAttributes::Vt320(
|
||||
DeviceAttributeFlags::from_params(¶ms[1..]),
|
||||
DeviceAttributeFlags::from_params(¶ms[2..]),
|
||||
)),
|
||||
))
|
||||
} else if !params.is_empty() && params[0] == CsiParam::Integer(64) {
|
||||
Ok(self.advance_by(
|
||||
)),
|
||||
[_, CsiParam::Integer(64), ..] => Ok(self.advance_by(
|
||||
params.len(),
|
||||
params,
|
||||
Device::DeviceAttributes(DeviceAttributes::Vt420(
|
||||
DeviceAttributeFlags::from_params(¶ms[1..]),
|
||||
DeviceAttributeFlags::from_params(¶ms[2..]),
|
||||
)),
|
||||
))
|
||||
} else {
|
||||
Err(())
|
||||
)),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse extended mouse reports known as SGR 1006 mode
|
||||
fn mouse_sgr1006(&mut self, params: &'a [CsiParam]) -> Result<MouseReport, ()> {
|
||||
if params.len() != 3 {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let p0 = params[0].as_integer().unwrap();
|
||||
let (p0, p1, p2) = match params {
|
||||
[CsiParam::P(b'<'), CsiParam::Integer(p0), CsiParam::P(b';'), CsiParam::Integer(p1), CsiParam::P(b';'), CsiParam::Integer(p2)] => {
|
||||
(*p0, *p1, *p2)
|
||||
}
|
||||
_ => return Err(()),
|
||||
};
|
||||
|
||||
// 'M' encodes a press, 'm' a release.
|
||||
let button = match (self.control, p0 & 0b110_0011) {
|
||||
@ -1845,11 +1839,8 @@ impl<'a> CSIParser<'a> {
|
||||
modifiers |= Modifiers::CTRL;
|
||||
}
|
||||
|
||||
let p1 = params[1].as_integer().unwrap();
|
||||
let p2 = params[2].as_integer().unwrap();
|
||||
|
||||
Ok(self.advance_by(
|
||||
3,
|
||||
6,
|
||||
params,
|
||||
MouseReport::SGR1006 {
|
||||
x: p1 as u16,
|
||||
@ -1889,101 +1880,148 @@ impl<'a> CSIParser<'a> {
|
||||
}
|
||||
|
||||
fn parse_sgr_color(&mut self, params: &'a [CsiParam]) -> Result<ColorSpec, ()> {
|
||||
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_8bpc(red, green, blue).into();
|
||||
Ok(self.advance_by(5, params, res))
|
||||
} 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(())
|
||||
match params {
|
||||
[_, CsiParam::P(b':'), CsiParam::Integer(2), CsiParam::P(b':'),
|
||||
CsiParam::Integer(_colorspace), CsiParam::P(b':'),
|
||||
red, CsiParam::P(b':'), green, CsiParam::P(b':'), blue, ..] => {
|
||||
let res = RgbColor::new_8bpc(to_u8(red)?, to_u8(green)?, to_u8(blue)?).into();
|
||||
Ok(self.advance_by(11, params, res))
|
||||
}
|
||||
|
||||
[_, CsiParam::P(b':'), CsiParam::Integer(2), CsiParam::P(b':'), /* empty colorspace */ CsiParam::P(b':'), red, CsiParam::P(b':'), green, CsiParam::P(b':'), blue, ..] => {
|
||||
let res = RgbColor::new_8bpc(to_u8(red)?, to_u8(green)?, to_u8(blue)?).into();
|
||||
Ok(self.advance_by(10, params, res))
|
||||
}
|
||||
|
||||
[_, CsiParam::P(b';'), CsiParam::Integer(2), CsiParam::P(b';'), red, CsiParam::P(b';'), green, CsiParam::P(b';'), blue, ..] |
|
||||
[_, CsiParam::P(b':'), CsiParam::Integer(2), CsiParam::P(b':'), red, CsiParam::P(b':'), green, CsiParam::P(b':'), blue, ..] =>
|
||||
{
|
||||
let res = RgbColor::new_8bpc(to_u8(red)?, to_u8(green)?, to_u8(blue)?).into();
|
||||
Ok(self.advance_by(9, params, res))
|
||||
}
|
||||
|
||||
[_, CsiParam::P(b';'), CsiParam::Integer(5), CsiParam::P(b';'), idx, ..] |
|
||||
[_, CsiParam::P(b':'), CsiParam::Integer(5), CsiParam::P(b':'), idx, ..] => {
|
||||
Ok(self.advance_by(5, params, ColorSpec::PaletteIndex(to_u8(idx)?)))
|
||||
}
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn window(&mut self, params: &'a [CsiParam]) -> Result<Window, ()> {
|
||||
if params.is_empty() {
|
||||
Err(())
|
||||
} else {
|
||||
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(()),
|
||||
},
|
||||
let (p, arg1, arg2) = match params {
|
||||
[CsiParam::Integer(p), CsiParam::P(b';'), CsiParam::Integer(arg1), CsiParam::P(b';'), CsiParam::Integer(arg2)] => {
|
||||
(*p, Some(*arg1), Some(*arg2))
|
||||
}
|
||||
[CsiParam::Integer(p), CsiParam::P(b';'), CsiParam::P(b';'), CsiParam::Integer(arg2)] => {
|
||||
(*p, None, Some(*arg2))
|
||||
}
|
||||
[CsiParam::Integer(p), CsiParam::P(b';'), CsiParam::Integer(arg1), CsiParam::P(b';')] => {
|
||||
(*p, Some(*arg1), None)
|
||||
}
|
||||
[CsiParam::Integer(p), CsiParam::P(b';'), CsiParam::P(b';')]
|
||||
| [CsiParam::Integer(p), CsiParam::P(b';')]
|
||||
| [CsiParam::Integer(p)] => (*p, None, None),
|
||||
_ => {
|
||||
return Err(());
|
||||
}
|
||||
};
|
||||
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),
|
||||
_ => Ok(Window::ReportCellSizePixelsResponse {
|
||||
height: arg1,
|
||||
width: arg2,
|
||||
}),
|
||||
},
|
||||
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 underline(&mut self, params: &'a [CsiParam]) -> Result<Sgr, ()> {
|
||||
let (sgr, n) = match params {
|
||||
[_, CsiParam::P(b':'), CsiParam::Integer(0), ..] => {
|
||||
(Sgr::Underline(Underline::None), 3)
|
||||
}
|
||||
[_, CsiParam::P(b':'), CsiParam::Integer(1), ..] => {
|
||||
(Sgr::Underline(Underline::Single), 3)
|
||||
}
|
||||
[_, CsiParam::P(b':'), CsiParam::Integer(2), ..] => {
|
||||
(Sgr::Underline(Underline::Double), 3)
|
||||
}
|
||||
[_, CsiParam::P(b':'), CsiParam::Integer(3), ..] => {
|
||||
(Sgr::Underline(Underline::Curly), 3)
|
||||
}
|
||||
[_, CsiParam::P(b':'), CsiParam::Integer(4), ..] => {
|
||||
(Sgr::Underline(Underline::Dotted), 3)
|
||||
}
|
||||
[_, CsiParam::P(b':'), CsiParam::Integer(5), ..] => {
|
||||
(Sgr::Underline(Underline::Dashed), 3)
|
||||
}
|
||||
_ => (Sgr::Underline(Underline::Single), 1),
|
||||
};
|
||||
|
||||
Ok(self.advance_by(n, params, sgr))
|
||||
}
|
||||
|
||||
fn sgr(&mut self, params: &'a [CsiParam]) -> Result<Sgr, ()> {
|
||||
if params.is_empty() {
|
||||
// With no parameters, treat as equivalent to Reset.
|
||||
@ -1997,6 +2035,7 @@ impl<'a> CSIParser<'a> {
|
||||
}
|
||||
|
||||
match ¶ms[0] {
|
||||
CsiParam::P(_) => Err(()),
|
||||
CsiParam::Integer(i) => match FromPrimitive::from_i64(*i) {
|
||||
None => Err(()),
|
||||
Some(sgr) => match sgr {
|
||||
@ -2004,7 +2043,9 @@ impl<'a> CSIParser<'a> {
|
||||
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::UnderlineOn => {
|
||||
self.underline(params) //.map(Sgr::Underline)
|
||||
}
|
||||
SgrCode::UnderlineDouble => one!(Sgr::Underline(Underline::Double)),
|
||||
SgrCode::UnderlineOff => one!(Sgr::Underline(Underline::None)),
|
||||
SgrCode::UnderlineColor => {
|
||||
@ -2116,53 +2157,6 @@ impl<'a> CSIParser<'a> {
|
||||
SgrCode::AltFont9 => one!(Sgr::Font(Font::Alternate(9))),
|
||||
},
|
||||
},
|
||||
CsiParam::ColonList(list) => {
|
||||
match list.as_slice() {
|
||||
// Kitty styled underlines
|
||||
&[Some(4), Some(0)] => one!(Sgr::Underline(Underline::None)),
|
||||
&[Some(4), Some(1)] => one!(Sgr::Underline(Underline::Single)),
|
||||
&[Some(4), Some(2)] => one!(Sgr::Underline(Underline::Double)),
|
||||
&[Some(4), Some(3)] => one!(Sgr::Underline(Underline::Curly)),
|
||||
&[Some(4), Some(4)] => one!(Sgr::Underline(Underline::Dotted)),
|
||||
&[Some(4), Some(5)] => one!(Sgr::Underline(Underline::Dashed)),
|
||||
|
||||
&[Some(38), Some(2), _colorspace, Some(r), Some(g), Some(b)] => one!(
|
||||
Sgr::Foreground(RgbColor::new_8bpc(r as u8, g as u8, b as u8).into())
|
||||
),
|
||||
&[Some(38), Some(2), Some(r), Some(g), Some(b)] => one!(Sgr::Foreground(
|
||||
RgbColor::new_8bpc(r as u8, g as u8, b as u8).into()
|
||||
)),
|
||||
&[Some(38), Some(5), Some(idx)] => {
|
||||
one!(Sgr::Foreground(ColorSpec::PaletteIndex(idx as u8)))
|
||||
}
|
||||
|
||||
&[Some(48), Some(2), _colorspace, Some(r), Some(g), Some(b)] => one!(
|
||||
Sgr::Background(RgbColor::new_8bpc(r as u8, g as u8, b as u8).into())
|
||||
),
|
||||
&[Some(48), Some(2), Some(r), Some(g), Some(b)] => one!(Sgr::Background(
|
||||
RgbColor::new_8bpc(r as u8, g as u8, b as u8).into()
|
||||
)),
|
||||
&[Some(48), Some(5), Some(idx)] => {
|
||||
one!(Sgr::Background(ColorSpec::PaletteIndex(idx as u8)))
|
||||
}
|
||||
|
||||
&[Some(58), Some(2), _colorspace, Some(r), Some(g), Some(b)] => {
|
||||
one!(Sgr::UnderlineColor(
|
||||
RgbColor::new_8bpc(r as u8, g as u8, b as u8).into()
|
||||
))
|
||||
}
|
||||
&[Some(58), Some(2), Some(r), Some(g), Some(b)] => {
|
||||
one!(Sgr::UnderlineColor(
|
||||
RgbColor::new_8bpc(r as u8, g as u8, b as u8).into()
|
||||
))
|
||||
}
|
||||
&[Some(58), Some(5), Some(idx)] => {
|
||||
one!(Sgr::UnderlineColor(ColorSpec::PaletteIndex(idx as u8)))
|
||||
}
|
||||
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2276,22 +2270,29 @@ mod test {
|
||||
use std::io::Write;
|
||||
|
||||
fn parse(control: char, params: &[i64], expected: &str) -> Vec<CSI> {
|
||||
let params = params
|
||||
.iter()
|
||||
.map(|&i| CsiParam::Integer(i))
|
||||
.collect::<Vec<_>>();
|
||||
let res = CSI::parse(¶ms, &[], false, control).collect();
|
||||
let mut cparams = vec![];
|
||||
for &p in params {
|
||||
if !cparams.is_empty() {
|
||||
cparams.push(CsiParam::P(b';'));
|
||||
}
|
||||
cparams.push(CsiParam::Integer(p));
|
||||
}
|
||||
let res = CSI::parse(&cparams, &[], false, control).collect();
|
||||
println!("parsed -> {:#?}", res);
|
||||
assert_eq!(encode(&res), expected);
|
||||
res
|
||||
}
|
||||
|
||||
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 mut cparams = vec![];
|
||||
for &p in params {
|
||||
if !cparams.is_empty() {
|
||||
cparams.push(CsiParam::P(b';'));
|
||||
}
|
||||
cparams.push(CsiParam::Integer(p));
|
||||
}
|
||||
let intermediates = [intermediate];
|
||||
let res = CSI::parse(¶ms, &intermediates, false, control).collect();
|
||||
let res = CSI::parse(&cparams, &intermediates, false, control).collect();
|
||||
assert_eq!(encode(&res), expected);
|
||||
res
|
||||
}
|
||||
@ -2340,7 +2341,12 @@ mod test {
|
||||
vec![
|
||||
CSI::Sgr(Sgr::Intensity(Intensity::Bold)),
|
||||
CSI::Unspecified(Box::new(Unspecified {
|
||||
params: [CsiParam::Integer(1231231), CsiParam::Integer(3)].to_vec(),
|
||||
params: [
|
||||
CsiParam::Integer(1231231),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(3)
|
||||
]
|
||||
.to_vec(),
|
||||
intermediates: vec![],
|
||||
ignored_extra_intermediates: false,
|
||||
control: 'm',
|
||||
@ -2350,7 +2356,12 @@ mod test {
|
||||
assert_eq!(
|
||||
parse('m', &[1231231, 3], "\x1b[1231231;3m"),
|
||||
vec![CSI::Unspecified(Box::new(Unspecified {
|
||||
params: [CsiParam::Integer(1231231), CsiParam::Integer(3)].to_vec(),
|
||||
params: [
|
||||
CsiParam::Integer(1231231),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(3)
|
||||
]
|
||||
.to_vec(),
|
||||
intermediates: vec![],
|
||||
ignored_extra_intermediates: false,
|
||||
control: 'm',
|
||||
@ -2375,7 +2386,12 @@ mod test {
|
||||
assert_eq!(
|
||||
parse('m', &[58, 2], "\x1b[58;2m"),
|
||||
vec![CSI::Unspecified(Box::new(Unspecified {
|
||||
params: [CsiParam::Integer(58), CsiParam::Integer(2)].to_vec(),
|
||||
params: [
|
||||
CsiParam::Integer(58),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(2)
|
||||
]
|
||||
.to_vec(),
|
||||
intermediates: vec![],
|
||||
ignored_extra_intermediates: false,
|
||||
control: 'm',
|
||||
@ -2393,7 +2409,12 @@ mod test {
|
||||
vec![
|
||||
CSI::Sgr(Sgr::UnderlineColor(ColorSpec::PaletteIndex(220))),
|
||||
CSI::Unspecified(Box::new(Unspecified {
|
||||
params: [CsiParam::Integer(255), CsiParam::Integer(255)].to_vec(),
|
||||
params: [
|
||||
CsiParam::Integer(255),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(255)
|
||||
]
|
||||
.to_vec(),
|
||||
intermediates: vec![],
|
||||
ignored_extra_intermediates: false,
|
||||
control: 'm',
|
||||
@ -2407,7 +2428,12 @@ mod test {
|
||||
assert_eq!(
|
||||
parse('m', &[38, 2], "\x1b[38;2m"),
|
||||
vec![CSI::Unspecified(Box::new(Unspecified {
|
||||
params: [CsiParam::Integer(38), CsiParam::Integer(2)].to_vec(),
|
||||
params: [
|
||||
CsiParam::Integer(38),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(2)
|
||||
]
|
||||
.to_vec(),
|
||||
intermediates: vec![],
|
||||
ignored_extra_intermediates: false,
|
||||
control: 'm',
|
||||
@ -2425,7 +2451,12 @@ mod test {
|
||||
vec![
|
||||
CSI::Sgr(Sgr::Foreground(ColorSpec::PaletteIndex(220))),
|
||||
CSI::Unspecified(Box::new(Unspecified {
|
||||
params: [CsiParam::Integer(255), CsiParam::Integer(255)].to_vec(),
|
||||
params: [
|
||||
CsiParam::Integer(255),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(255)
|
||||
]
|
||||
.to_vec(),
|
||||
intermediates: vec![],
|
||||
ignored_extra_intermediates: false,
|
||||
control: 'm',
|
||||
@ -2510,6 +2541,14 @@ mod test {
|
||||
DecPrivateMode::Unspecified(23434),
|
||||
))]
|
||||
);
|
||||
|
||||
/*
|
||||
{
|
||||
let res = CSI::parse(&[CsiParam::Integer(2026)], &[b'?', b'$'], false, 'p').collect();
|
||||
assert_eq!(encode(&res), "\x1b[?2026$p");
|
||||
}
|
||||
*/
|
||||
|
||||
assert_eq!(
|
||||
parse_int('l', &[1], b'?', "\x1b[?1l"),
|
||||
vec![CSI::Mode(Mode::ResetDecPrivateMode(DecPrivateMode::Code(
|
||||
@ -2565,8 +2604,23 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn mouse() {
|
||||
let res: Vec<_> = CSI::parse(
|
||||
&[
|
||||
CsiParam::P(b'<'),
|
||||
CsiParam::Integer(0),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(12),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(300),
|
||||
],
|
||||
&[],
|
||||
false,
|
||||
'M',
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(encode(&res), "\x1b[<0;12;300M");
|
||||
assert_eq!(
|
||||
parse_int('M', &[0, 12, 300], b'<', "\x1b[<0;12;300M"),
|
||||
res,
|
||||
vec![CSI::Mouse(MouseReport::SGR1006 {
|
||||
x: 12,
|
||||
y: 300,
|
||||
@ -2578,13 +2632,33 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn device_attr() {
|
||||
let res: Vec<_> = CSI::parse(
|
||||
&[
|
||||
CsiParam::P(b'?'),
|
||||
CsiParam::Integer(63),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(1),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(2),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(4),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(6),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(9),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(15),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(22),
|
||||
],
|
||||
&[],
|
||||
false,
|
||||
'c',
|
||||
)
|
||||
.collect();
|
||||
|
||||
assert_eq!(
|
||||
parse_int(
|
||||
'c',
|
||||
&[63, 1, 2, 4, 6, 9, 15, 22],
|
||||
b'?',
|
||||
"\x1b[?63;1;2;4;6;9;15;22c"
|
||||
),
|
||||
res,
|
||||
vec![CSI::Device(Box::new(Device::DeviceAttributes(
|
||||
DeviceAttributes::Vt320(DeviceAttributeFlags::new(vec![
|
||||
DeviceAttribute::Code(DeviceAttributeCodes::Columns132),
|
||||
@ -2597,5 +2671,6 @@ mod test {
|
||||
])),
|
||||
)))]
|
||||
);
|
||||
assert_eq!(encode(&res), "\x1b[?63;1;2;4;6;9;15;22c");
|
||||
}
|
||||
}
|
||||
|
@ -291,7 +291,7 @@ impl VTActor for CollectingVTActor {
|
||||
|
||||
const MAX_INTERMEDIATES: usize = 2;
|
||||
const MAX_OSC: usize = 16;
|
||||
const MAX_PARAMS: usize = 16;
|
||||
const MAX_PARAMS: usize = 32;
|
||||
|
||||
struct OscState {
|
||||
buffer: Vec<u8>,
|
||||
@ -355,11 +355,17 @@ pub struct VTParser {
|
||||
/// extend the meaning. For example: `CSI 4:3 m` is a sequence used
|
||||
/// to denote a curly underline. That would be represented as:
|
||||
/// `[CsiParam::ColonList(vec![Some(4), Some(3)])]`.
|
||||
///
|
||||
/// Later: reading ECMA 48, CSI is defined as:
|
||||
/// CSI P ... P I ... I F
|
||||
/// Where P are parameter bytes in the range 0x30-0x3F [0-9:;<=>?]
|
||||
/// and I are intermediate bytes in the range 0x20-0x2F
|
||||
/// and F is the final byte in the range 0x40-0x7E
|
||||
///
|
||||
#[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
|
||||
P(u8),
|
||||
}
|
||||
|
||||
impl Default for CsiParam {
|
||||
@ -368,29 +374,7 @@ impl Default for CsiParam {
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
@ -405,15 +389,8 @@ impl std::fmt::Display for CsiParam {
|
||||
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)?;
|
||||
}
|
||||
}
|
||||
CsiParam::P(p) => {
|
||||
write!(f, "{}", *p as char)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@ -451,13 +428,11 @@ 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),
|
||||
let mut i = 0;
|
||||
for src in &self.params[0..self.num_params] {
|
||||
if let CsiParam::Integer(value) = src {
|
||||
res[i] = *value;
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
res
|
||||
@ -498,31 +473,29 @@ impl VTParser {
|
||||
if self.params_full {
|
||||
return;
|
||||
}
|
||||
if param == b';' {
|
||||
if self.num_params + 1 > MAX_OSC {
|
||||
self.params_full = true;
|
||||
} else {
|
||||
// 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;
|
||||
match param {
|
||||
b'0'..=b'9' => match self.current_param.take() {
|
||||
Some(CsiParam::Integer(i)) => {
|
||||
self.current_param.replace(CsiParam::Integer(
|
||||
i.saturating_mul(10).saturating_add((param - b'0') as i64),
|
||||
));
|
||||
}
|
||||
Some(_) => unreachable!(),
|
||||
None => {
|
||||
self.current_param
|
||||
.replace(CsiParam::Integer((param - b'0') as i64));
|
||||
}
|
||||
},
|
||||
p => {
|
||||
self.finish_param();
|
||||
|
||||
if self.num_params + 1 > MAX_PARAMS {
|
||||
self.params_full = true;
|
||||
} else {
|
||||
self.params[self.num_params] = CsiParam::P(p);
|
||||
self.num_params += 1;
|
||||
}
|
||||
}
|
||||
} 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,
|
||||
};
|
||||
|
||||
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 => {
|
||||
@ -826,7 +799,11 @@ mod test {
|
||||
// This is the kitty curly underline sequence.
|
||||
parse_as_vec(b"\x1b[4:3m"),
|
||||
vec![VTAction::CsiDispatch {
|
||||
params: vec![CsiParam::ColonList(vec![Some(4), Some(3)])],
|
||||
params: vec![
|
||||
CsiParam::Integer(4),
|
||||
CsiParam::P(b':'),
|
||||
CsiParam::Integer(3)
|
||||
],
|
||||
intermediates: b"".to_vec(),
|
||||
ignored_excess_intermediates: false,
|
||||
byte: b'm'
|
||||
@ -839,14 +816,18 @@ mod test {
|
||||
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)
|
||||
])],
|
||||
params: vec![
|
||||
CsiParam::Integer(38),
|
||||
CsiParam::P(b':'),
|
||||
CsiParam::Integer(2),
|
||||
CsiParam::P(b':'),
|
||||
CsiParam::P(b':'),
|
||||
CsiParam::Integer(128),
|
||||
CsiParam::P(b':'),
|
||||
CsiParam::Integer(64),
|
||||
CsiParam::P(b':'),
|
||||
CsiParam::Integer(192),
|
||||
],
|
||||
intermediates: b"".to_vec(),
|
||||
ignored_excess_intermediates: false,
|
||||
byte: b'm'
|
||||
@ -859,8 +840,7 @@ mod test {
|
||||
assert_eq!(
|
||||
parse_as_vec(b"\x1b[;1m"),
|
||||
vec![VTAction::CsiDispatch {
|
||||
// The omitted parameter defaults to 0
|
||||
params: vec![CsiParam::Integer(0), CsiParam::Integer(1)],
|
||||
params: vec![CsiParam::P(b';'), CsiParam::Integer(1)],
|
||||
intermediates: b"".to_vec(),
|
||||
ignored_excess_intermediates: false,
|
||||
byte: b'm'
|
||||
@ -873,10 +853,40 @@ 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: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 51]
|
||||
.iter()
|
||||
.map(|&i| CsiParam::Integer(i))
|
||||
.collect(),
|
||||
params: vec![
|
||||
CsiParam::Integer(0),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(1),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(2),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(3),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(4),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(5),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(6),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(7),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(8),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(9),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(0),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(1),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(2),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(3),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(4),
|
||||
CsiParam::P(b';'),
|
||||
CsiParam::Integer(51),
|
||||
CsiParam::P(b';'),
|
||||
],
|
||||
intermediates: b"".to_vec(),
|
||||
ignored_excess_intermediates: false,
|
||||
byte: b'p'
|
||||
|
Loading…
Reference in New Issue
Block a user