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]]
name = "vtparse"
version = "0.3.0"
version = "0.4.0"
dependencies = [
"pretty_assertions",
"utf8parse",

View File

@ -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"]

View File

@ -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<i64>,
pub params: Vec<CsiParam>,
// 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
/// 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) {
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(*p as u16)),
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<Self, ()>;
fn parse_params(params: &[CsiParam]) -> Result<Self, ()>;
}
/// Parse an input parameter into a 1-based unsigned value
impl ParseParams for u32 {
fn parse_params(params: &[i64]) -> Result<u32, ()> {
fn parse_params(params: &[CsiParam]) -> Result<u32, ()> {
if params.is_empty() {
Ok(1)
} else if params.len() == 1 {
to_1b_u32(params[0])
to_1b_u32(&params[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<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(params[0])
OneBased::from_esc_param(&params[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(&params[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(&params[0])?,
OneBased::from_esc_param(&params[1])?,
))
} else {
Err(())
@ -1000,11 +1005,14 @@ trait ParamEnum: FromPrimitive {
/// implement ParseParams for the enums that also implement ParamEnum.
impl<T: ParamEnum> ParseParams for T {
fn parse_params(params: &[i64]) -> Result<Self, ()> {
fn parse_params(params: &[CsiParam]) -> Result<Self, ()> {
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,12 +1291,17 @@ impl CSI {
}
/// A little helper to convert i64 -> u8 if safe
fn to_u8(v: i64) -> Result<u8, ()> {
if v <= i64::from(u8::max_value()) {
Ok(v as u8)
fn to_u8(v: &CsiParam) -> Result<u8, ()> {
match v {
CsiParam::ColonList(_) => Err(()),
CsiParam::Integer(v) => {
if *v <= i64::from(u8::max_value()) {
Ok(*v as u8)
} else {
Err(())
}
}
}
}
/// Convert the input value to 1-based u32.
@ -1302,13 +1315,11 @@ fn to_u8(v: i64) -> Result<u8, ()> {
/// 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<u32, ()> {
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<u32, ()> {
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<CSI, ()> {
fn parse_next(&mut self, params: &'a [CsiParam]) -> Result<CSI, ()> {
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<i64, ()> {
params.get(idx).cloned().ok_or(())
fn p(params: &[CsiParam], idx: usize) -> Result<i64, ()> {
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<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);
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<CSI, ()> {
fn cursor_style(&mut self, params: &'a [CsiParam]) -> Result<CSI, ()> {
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<CSI, ()> {
if params == [5] {
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 == [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<CSI, ()> {
fn decstbm(&mut self, params: &'a [CsiParam]) -> Result<CSI, ()> {
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(&params[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(&params[0])?,
bottom: OneBased::from_esc_param_with_big_default(&params[1])?,
}),
))
} 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 {
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<CSI, ()> {
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.
@ -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(&params[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(&params[0])?,
right: OneBased::from_esc_param(&params[1])?,
}),
))
} 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 == [] {
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<Device, ()> {
fn req_terminal_name_and_version(&mut self, params: &'a [CsiParam]) -> Result<Device, ()> {
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<Device, ()> {
fn req_secondary_device_attributes(&mut self, params: &'a [CsiParam]) -> Result<Device, ()> {
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<Device, ()> {
if params == [1, 0] {
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 == [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(&params[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(&params[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<MouseReport, ()> {
fn mouse_sgr1006(&mut self, params: &'a [CsiParam]) -> Result<MouseReport, ()> {
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<DecPrivateMode, ()> {
let p0 = *params.get(0).ok_or_else(|| ())?;
fn dec(&mut self, params: &'a [CsiParam]) -> Result<DecPrivateMode, ()> {
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<TerminalMode, ()> {
let p0 = *params.get(0).ok_or_else(|| ())?;
fn terminal_mode(&mut self, params: &'a [CsiParam]) -> Result<TerminalMode, ()> {
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,28 +1740,30 @@ impl<'a> CSIParser<'a> {
}
}
fn parse_sgr_color(&mut self, params: &'a [i64]) -> Result<ColorSpec, ()> {
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<ColorSpec, ()> {
if params.len() >= 5 && params[1].as_integer() == Some(2) {
let red = to_u8(&params[2])?;
let green = to_u8(&params[3])?;
let blue = to_u8(&params[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(&params[2])?;
Ok(self.advance_by(3, params, ColorSpec::PaletteIndex(idx)))
} else {
Err(())
}
}
fn window(&mut self, params: &'a [i64]) -> Result<Window, ()> {
fn window(&mut self, params: &'a [CsiParam]) -> Result<Window, ()> {
if params.is_empty() {
Err(())
} else {
let arg1 = params.get(1).cloned();
let arg2 = params.get(2).cloned();
match params[0] {
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 {
@ -1805,11 +1831,12 @@ impl<'a> CSIParser<'a> {
_ => Err(()),
},
_ => Err(()),
},
}
}
}
fn sgr(&mut self, params: &'a [i64]) -> Result<Sgr, ()> {
fn sgr(&mut self, params: &'a [CsiParam]) -> Result<Sgr, ()> {
if params.is_empty() {
// With no parameters, treat as equivalent to Reset.
Ok(Sgr::Reset)
@ -1821,7 +1848,8 @@ impl<'a> CSIParser<'a> {
};
};
match FromPrimitive::from_i64(params[0]) {
match params[0] {
CsiParam::Integer(i) => match FromPrimitive::from_i64(i) {
None => Err(()),
Some(sgr) => match sgr {
SgrCode::Reset => one!(Sgr::Reset),
@ -1837,58 +1865,88 @@ impl<'a> CSIParser<'a> {
SgrCode::UnderlineColor => {
self.parse_sgr_color(params).map(Sgr::UnderlineColor)
}
SgrCode::ResetUnderlineColor => one!(Sgr::UnderlineColor(ColorSpec::default())),
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::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::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::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::ForegroundBrightBlue => {
one!(Sgr::Foreground(AnsiColor::Blue.into()))
}
SgrCode::ForegroundBrightMagenta => {
one!(Sgr::Foreground(AnsiColor::Fuschia.into()))
}
SgrCode::ForegroundBrightCyan => one!(Sgr::Foreground(AnsiColor::Aqua.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 => {
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::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::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::BackgroundBrightBlue => {
one!(Sgr::Background(AnsiColor::Blue.into()))
}
SgrCode::BackgroundBrightMagenta => {
one!(Sgr::Background(AnsiColor::Fuschia.into()))
}
SgrCode::BackgroundBrightCyan => one!(Sgr::Background(AnsiColor::Aqua.into())),
SgrCode::BackgroundBrightCyan => {
one!(Sgr::Background(AnsiColor::Aqua.into()))
}
SgrCode::BackgroundBrightWhite => {
one!(Sgr::Background(AnsiColor::White.into()))
}
@ -1912,6 +1970,8 @@ impl<'a> CSIParser<'a> {
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<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);
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 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);
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',

View File

@ -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<Self, ()> {
if v == 0 {
Ok(Self {
pub fn from_esc_param(v: &CsiParam) -> Result<Self, ()> {
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<Self, ()> {
if v == 0 {
Ok(Self {
pub fn from_esc_param_with_big_default(v: &CsiParam) -> Result<Self, ()> {
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, ()> {
Self::from_esc_param(o.cloned().unwrap_or(1))
pub fn from_optional_esc_param(o: Option<&CsiParam>) -> Result<Self, ()> {
Self::from_esc_param(o.unwrap_or(&CsiParam::Integer(1)))
}
/// Return the underlying value as a 0-based value

View File

@ -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

View File

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

View File

@ -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<i64>,
params: Vec<CsiParam>,
intermediates: Vec<u8>,
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<i64>,
current_param: Option<CsiParam>,
params_full: bool,
utf8_parser: Utf8Parser,
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 {
#[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,

View File

@ -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),