1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-25 21:07:39 +03:00

vtparse: parse APC sequences

These were parsed but swallowed. This commit expands the transitions
to be able to track the APC start, data and end and then adds
an `apc_dispatch` method to allow capturing APC sequences.

APC sequences are used in the kitty image protocol.

refs: #986
This commit is contained in:
Wez Furlong 2021-07-26 18:43:45 -07:00
parent 227e375a5f
commit de1298f8b2
4 changed files with 109 additions and 49 deletions

View File

@ -185,6 +185,10 @@ impl<'a, F: FnMut(Action)> VTActor for Performer<'a, F> {
}
}
fn apc_dispatch(&mut self, _data: Vec<u8>) {
// Ignored
}
fn dcs_hook(
&mut self,
byte: u8,

View File

@ -1,6 +1,7 @@
#![allow(dead_code)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[repr(u8)]
#[allow(dead_code)]
#[repr(u16)]
pub enum Action {
None = 0,
Ignore = 1,
@ -18,19 +19,20 @@ pub enum Action {
OscPut = 13,
OscEnd = 14,
Utf8 = 15,
ApcStart = 16,
ApcPut = 17,
ApcEnd = 18,
}
impl Action {
#[inline(always)]
#[allow(dead_code)]
pub fn from_u8(v: u8) -> Self {
pub fn from_u16(v: u16) -> Self {
unsafe { std::mem::transmute(v) }
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[repr(u8)]
#[allow(dead_code)]
#[repr(u16)]
pub enum State {
Ground = 0,
Escape = 1,
@ -45,15 +47,16 @@ pub enum State {
DcsPassthrough = 10,
DcsIgnore = 11,
OscString = 12,
SosPmApcString = 13,
Anywhere = 14,
Utf8Sequence = 15,
SosPmString = 13,
ApcString = 14,
// Special states, always last (no tables for these)
Anywhere = 15,
Utf8Sequence = 16,
}
impl State {
#[inline(always)]
#[allow(dead_code)]
pub fn from_u8(v: u8) -> Self {
pub fn from_u16(v: u16) -> Self {
unsafe { std::mem::transmute(v) }
}
}

View File

@ -25,7 +25,7 @@ fn lookup(state: State, b: u8) -> (Action, State) {
.get_unchecked(state as usize)
.get_unchecked(b as usize)
};
(Action::from_u8(v >> 4), State::from_u8(v & 0xf))
(Action::from_u16(v >> 8), State::from_u16(v & 0xff))
}
#[inline(always)]
@ -158,6 +158,10 @@ pub trait VTActor {
/// that were passed as semicolon separated parameters to the operating
/// system command.
fn osc_dispatch(&mut self, params: &[&[u8]]);
/// Called when an APC string is terminated by ST
/// `data` is the data contained within the APC sequence.
fn apc_dispatch(&mut self, data: Vec<u8>);
}
/// `VTAction` is an alternative way to work with the parser; rather
@ -187,6 +191,7 @@ pub enum VTAction {
byte: u8,
},
OscDispatch(Vec<Vec<u8>>),
ApcDispatch(Vec<u8>),
}
/// This is an implementation of `VTActor` that captures the events
@ -273,6 +278,10 @@ impl VTActor for CollectingVTActor {
params.iter().map(|i| i.to_vec()).collect(),
));
}
fn apc_dispatch(&mut self, data: Vec<u8>) {
self.actions.push(VTAction::ApcDispatch(data));
}
}
const MAX_INTERMEDIATES: usize = 2;
@ -324,6 +333,7 @@ pub struct VTParser {
num_params: usize,
current_param: Option<CsiParam>,
params_full: bool,
apc_data: Vec<u8>,
utf8_parser: Utf8Parser,
utf8_return_state: State,
@ -409,6 +419,7 @@ impl VTParser {
current_param: None,
utf8_parser: Utf8Parser::new(),
apc_data: vec![],
}
}
@ -465,6 +476,7 @@ impl VTParser {
self.num_params = 0;
self.params_full = false;
self.current_param.take();
self.apc_data.clear();
}
Action::Collect => {
if self.num_intermediates < MAX_INTERMEDIATES {
@ -562,6 +574,16 @@ impl VTParser {
}
}
Action::ApcStart => {
self.apc_data.clear();
}
Action::ApcPut => {
self.apc_data.push(param);
}
Action::ApcEnd => {
actor.apc_dispatch(std::mem::take(&mut self.apc_data));
}
Action::Utf8 => self.next_utf8(actor, param),
}
}
@ -1032,6 +1054,22 @@ mod test {
);
}
#[test]
fn kitty_img() {
assert_eq!(
parse_as_vec("\x1b_Gf=24,s=10,v=20;payload\x1b\\".as_bytes()),
vec![
VTAction::ApcDispatch(b"Gf=24,s=10,v=20;payload".to_vec()),
VTAction::EscDispatch {
params: vec![],
intermediates: vec![],
ignored_excess_intermediates: false,
byte: b'\\',
}
]
);
}
#[test]
fn sixel() {
assert_eq!(

View File

@ -3,10 +3,10 @@
use crate::enums::{Action, State};
/// Apply all u8 values to `fn(u8) -> u8`, return `[u8; 256]`.
/// Apply all u8 values to `fn(u8) -> u16`, return `[u16; 256]`.
macro_rules! define_table {
( $func:tt ) => {{
const fn gen() -> [u8; 256] {
const fn gen() -> [u16; 256] {
let mut arr = [0; 256];
let mut i = 0;
@ -20,11 +20,11 @@ macro_rules! define_table {
}};
}
const fn pack(action: Action, state: State) -> u8 {
((action as u8) << 4) | (state as u8)
const fn pack(action: Action, state: State) -> u16 {
((action as u16) << 8) | (state as u16)
}
const fn anywhere_or(i: u8, state: State) -> u8 {
const fn anywhere_or(i: u8, state: State) -> u16 {
use Action::*;
use State::*;
match i {
@ -36,9 +36,9 @@ const fn anywhere_or(i: u8, state: State) -> u8 {
0x9a => pack(Execute, Ground),
0x9c => pack(None, Ground),
0x1b => pack(None, Escape),
0x98 => pack(None, SosPmApcString),
0x9e => pack(None, SosPmApcString),
0x9f => pack(None, SosPmApcString),
0x98 => pack(None, SosPmString),
0x9e => pack(None, SosPmString),
0x9f => pack(None, SosPmString),
0x90 => pack(None, DcsEntry),
0x9d => pack(None, OscString),
0x9b => pack(None, CsiEntry),
@ -46,7 +46,7 @@ const fn anywhere_or(i: u8, state: State) -> u8 {
}
}
const fn ground(i: u8) -> u8 {
const fn ground(i: u8) -> u16 {
use Action::*;
use State::*;
match i {
@ -65,7 +65,7 @@ const fn ground(i: u8) -> u8 {
}
}
const fn escape(i: u8) -> u8 {
const fn escape(i: u8) -> u16 {
use Action::*;
use State::*;
match i {
@ -83,14 +83,14 @@ const fn escape(i: u8) -> u8 {
0x5b => pack(None, CsiEntry),
0x5d => pack(None, OscString),
0x50 => pack(None, DcsEntry),
0x58 => pack(None, SosPmApcString),
0x5e => pack(None, SosPmApcString),
0x5f => pack(None, SosPmApcString),
0x58 => pack(None, SosPmString),
0x5e => pack(None, SosPmString),
0x5f => pack(None, ApcString),
_ => anywhere_or(i, Escape),
}
}
const fn escape_intermediate(i: u8) -> u8 {
const fn escape_intermediate(i: u8) -> u16 {
use Action::*;
use State::*;
match i {
@ -104,7 +104,7 @@ const fn escape_intermediate(i: u8) -> u8 {
}
}
const fn csi_entry(i: u8) -> u8 {
const fn csi_entry(i: u8) -> u16 {
use Action::*;
use State::*;
match i {
@ -122,7 +122,7 @@ const fn csi_entry(i: u8) -> u8 {
}
}
const fn csi_param(i: u8) -> u8 {
const fn csi_param(i: u8) -> u16 {
use Action::*;
use State::*;
match i {
@ -138,7 +138,7 @@ const fn csi_param(i: u8) -> u8 {
}
}
const fn csi_intermediate(i: u8) -> u8 {
const fn csi_intermediate(i: u8) -> u16 {
use Action::*;
use State::*;
match i {
@ -153,7 +153,7 @@ const fn csi_intermediate(i: u8) -> u8 {
}
}
const fn csi_ignore(i: u8) -> u8 {
const fn csi_ignore(i: u8) -> u16 {
use Action::*;
use State::*;
match i {
@ -167,7 +167,7 @@ const fn csi_ignore(i: u8) -> u8 {
}
}
const fn dcs_entry(i: u8) -> u8 {
const fn dcs_entry(i: u8) -> u16 {
use Action::*;
use State::*;
match i {
@ -185,7 +185,7 @@ const fn dcs_entry(i: u8) -> u8 {
}
}
const fn dcs_param(i: u8) -> u8 {
const fn dcs_param(i: u8) -> u16 {
use Action::*;
use State::*;
match i {
@ -203,7 +203,7 @@ const fn dcs_param(i: u8) -> u8 {
}
}
const fn dcs_intermediate(i: u8) -> u8 {
const fn dcs_intermediate(i: u8) -> u16 {
use Action::*;
use State::*;
match i {
@ -218,7 +218,7 @@ const fn dcs_intermediate(i: u8) -> u8 {
}
}
const fn dcs_passthrough(i: u8) -> u8 {
const fn dcs_passthrough(i: u8) -> u16 {
use Action::*;
use State::*;
match i {
@ -231,7 +231,7 @@ const fn dcs_passthrough(i: u8) -> u8 {
}
}
const fn dcs_ignore(i: u8) -> u8 {
const fn dcs_ignore(i: u8) -> u16 {
use Action::*;
use State::*;
match i {
@ -243,7 +243,7 @@ const fn dcs_ignore(i: u8) -> u8 {
}
}
const fn osc_string(i: u8) -> u8 {
const fn osc_string(i: u8) -> u16 {
use Action::*;
use State::*;
match i {
@ -266,19 +266,31 @@ const fn osc_string(i: u8) -> u8 {
}
}
const fn sos_pm_apc_string(i: u8) -> u8 {
const fn sos_pm_string(i: u8) -> u16 {
use Action::*;
use State::*;
match i {
0x00..=0x17 => pack(Ignore, SosPmApcString),
0x19 => pack(Ignore, SosPmApcString),
0x1c..=0x1f => pack(Ignore, SosPmApcString),
0x20..=0x7f => pack(Ignore, SosPmApcString),
_ => anywhere_or(i, SosPmApcString),
0x00..=0x17 => pack(Ignore, SosPmString),
0x19 => pack(Ignore, SosPmString),
0x1c..=0x1f => pack(Ignore, SosPmString),
0x20..=0x7f => pack(Ignore, SosPmString),
_ => anywhere_or(i, SosPmString),
}
}
pub(crate) static TRANSITIONS: [[u8; 256]; 14] = [
const fn apc_string(i: u8) -> u16 {
use Action::*;
use State::*;
match i {
0x00..=0x17 => pack(ApcPut, ApcString),
0x19 => pack(ApcPut, ApcString),
0x1c..=0x1f => pack(ApcPut, ApcString),
0x20..=0x7f => pack(ApcPut, ApcString),
_ => anywhere_or(i, ApcString),
}
}
pub(crate) static TRANSITIONS: [[u16; 256]; 15] = [
define_table!(ground),
define_table!(escape),
define_table!(escape_intermediate),
@ -292,10 +304,11 @@ pub(crate) static TRANSITIONS: [[u8; 256]; 14] = [
define_table!(dcs_passthrough),
define_table!(dcs_ignore),
define_table!(osc_string),
define_table!(sos_pm_apc_string),
define_table!(sos_pm_string),
define_table!(apc_string),
];
pub(crate) static ENTRY: [Action; 14] = [
pub(crate) static ENTRY: [Action; 15] = [
Action::None, // Ground
Action::Clear, // Escape
Action::None, // EscapeIntermediate
@ -309,10 +322,11 @@ pub(crate) static ENTRY: [Action; 14] = [
Action::Hook, // DcsPassthrough
Action::None, // DcsIgnore
Action::OscStart, // OscString
Action::None, // SosPmApcString
Action::None, // SosPmString
Action::ApcStart, // ApcString
];
pub(crate) static EXIT: [Action; 14] = [
pub(crate) static EXIT: [Action; 15] = [
Action::None, // Ground
Action::None, // Escape
Action::None, // EscapeIntermediate
@ -326,7 +340,8 @@ pub(crate) static EXIT: [Action; 14] = [
Action::Unhook, // DcsPassthrough
Action::None, // DcsIgnore
Action::OscEnd, // OscString
Action::None, // SosPmApcString
Action::None, // SosPmString
Action::ApcEnd, // ApcString
];
#[cfg(test)]
@ -342,7 +357,7 @@ mod tests {
hash(&v, 5381, 33), // djb2
hash(&v, 0, 65599), // sdbm
),
(14021, 626090, 11884276359605205711, 6929800990073628062)
(17385, 799944, 12647816782590382477, 3641575052870461598)
);
}