use serde_json::Value;
use crate::wire::{Event, Reply};
pub const MAX_MESSAGE: usize = 32 << 20;
const PASTE_START: &[u8] = b"\x1b[200~";
const PASTE_END: &[u8] = b"\x1b[201~";
const APC_PREFIX: &[u8] = b"tsp;";
const OSC_PREFIX: &[u8] = b"877;tsp;";
#[derive(Clone, Debug, PartialEq)]
pub enum Item {
Keys(Vec<u8>),
Reply(Reply),
Event(Event),
Da1,
}
enum Scan {
Undecided,
Keys(usize),
Da1(usize),
Paste(usize),
Tsp(usize),
}
#[derive(Clone, Debug, Default)]
pub struct Parser {
buf: Vec<u8>,
paste: bool,
tsp: Option<usize>,
scanned: usize,
skipping: bool,
}
impl Parser {
pub fn new() -> Self {
Self::default()
}
pub const fn pending(&self) -> bool {
self.tsp.is_none() && !self.skipping && !self.paste && !self.buf.is_empty()
}
pub fn feed(&mut self, bytes: &[u8]) -> Vec<Item> {
let mut out = Vec::new();
self.buf.extend_from_slice(bytes);
self.run(&mut out);
out
}
pub fn flush(&mut self) -> Vec<Item> {
if self.pending() {
vec![Item::Keys(std::mem::take(&mut self.buf))]
} else {
Vec::new()
}
}
fn run(&mut self, out: &mut Vec<Item>) {
let mut keys: Vec<u8> = Vec::new();
let mut at = 0;
loop {
if self.skipping || self.tsp.is_some() {
match self.message(at, out, &mut keys) {
Some(next) => at = next,
None => break,
}
continue;
}
if self.paste {
let rest = &self.buf[at..];
if let Some(end) = find(rest, PASTE_END) {
keys.extend_from_slice(&rest[..end + PASTE_END.len()]);
at += end + PASTE_END.len();
self.paste = false;
continue;
}
let hold = partial_suffix(rest, PASTE_END);
keys.extend_from_slice(&rest[..rest.len() - hold]);
at = self.buf.len() - hold;
break;
}
let rest = &self.buf[at..];
if rest.is_empty() {
break;
}
let plain = rest.iter().position(|&b| b == 0x1b).unwrap_or(rest.len());
if plain > 0 {
keys.extend_from_slice(&rest[..plain]);
at += plain;
continue;
}
match scan(rest) {
Scan::Undecided => break,
Scan::Keys(n) => {
keys.extend_from_slice(&rest[..n]);
at += n;
},
Scan::Paste(n) => {
keys.extend_from_slice(&rest[..n]);
at += n;
self.paste = true;
},
Scan::Da1(n) => {
push_keys(out, &mut keys);
out.push(Item::Da1);
at += n;
},
Scan::Tsp(content) => {
self.buf.drain(..at);
self.tsp = Some(content);
self.scanned = content;
at = 0;
},
}
}
push_keys(out, &mut keys);
self.buf.drain(..at);
}
fn message(&mut self, at: usize, out: &mut Vec<Item>, keys: &mut Vec<u8>) -> Option<usize> {
debug_assert_eq!(at, 0, "a TSP message is rebased to the buffer start");
let from = self.scanned;
let Some((end, term_len)) = terminator(&self.buf[from..]).map(|(i, n)| (from + i, n)) else {
self.scanned = self.buf.len().saturating_sub(1).max(from);
if !self.skipping && self.buf.len() > MAX_MESSAGE {
tracing::warn!(bytes = self.buf.len(), "dropping an oversized TSP message");
self.skipping = true;
self.tsp = None;
}
if self.skipping {
let keep = usize::from(self.buf.last() == Some(&0x1b));
let len = self.buf.len();
self.buf.drain(..len - keep);
self.scanned = 0;
}
return None;
};
if !self.skipping
&& let Some(content) = self.tsp
{
push_keys(out, keys);
if let Some(item) = decode(&self.buf[content..end]) {
out.push(item);
}
}
self.skipping = false;
self.tsp = None;
self.scanned = 0;
self.buf.drain(..end + term_len);
Some(0)
}
}
fn push_keys(out: &mut Vec<Item>, keys: &mut Vec<u8>) {
if !keys.is_empty() {
out.push(Item::Keys(std::mem::take(keys)));
}
}
fn find(hay: &[u8], needle: &[u8]) -> Option<usize> {
hay.windows(needle.len()).position(|w| w == needle)
}
fn partial_suffix(hay: &[u8], needle: &[u8]) -> usize {
(1..needle.len())
.rev()
.find(|&n| hay.ends_with(&needle[..n]))
.unwrap_or(0)
}
fn terminator(bytes: &[u8]) -> Option<(usize, usize)> {
let mut i = 0;
while i < bytes.len() {
match bytes[i] {
0x07 => return Some((i, 1)),
0x1b if bytes.get(i + 1) == Some(&b'\\') => return Some((i, 2)),
_ => {},
}
i += 1;
}
None
}
fn scan(rest: &[u8]) -> Scan {
let Some(&second) = rest.get(1) else {
return Scan::Undecided;
};
match second {
b'_' => prefixed(rest, APC_PREFIX),
b']' => prefixed(rest, OSC_PREFIX),
b'[' => csi(rest),
_ => Scan::Keys(1),
}
}
fn prefixed(rest: &[u8], prefix: &[u8]) -> Scan {
let after = &rest[2..];
let n = after.len().min(prefix.len());
if after[..n] != prefix[..n] {
return Scan::Keys(2);
}
if n < prefix.len() {
return Scan::Undecided;
}
Scan::Tsp(2 + prefix.len())
}
fn csi(rest: &[u8]) -> Scan {
let mut i = 2;
while let Some(&b) = rest.get(i) {
match b {
0x20..=0x3f => i += 1,
0x40..=0x7e => {
let seq = &rest[..=i];
let params = &seq[2..i];
if b == b'c'
&& params.first() == Some(&b'?')
&& params[1..].iter().all(|&p| p.is_ascii_digit() || p == b';')
{
return Scan::Da1(i + 1);
}
if seq == PASTE_START {
return Scan::Paste(i + 1);
}
return Scan::Keys(i + 1);
},
_ => return Scan::Keys(i),
}
}
Scan::Undecided
}
fn decode(content: &[u8]) -> Option<Item> {
let semi = content.iter().position(|&b| b == b';')?;
let verb = &content[..semi];
let mut pos = semi + 1;
while let Some(len) = content[pos..].iter().position(|&b| b == b';') {
if !parameter(&content[pos..pos + len]) {
break;
}
pos += len + 1;
}
let body = &content[pos..];
let Ok(Value::Object(map)) = serde_json::from_slice::<Value>(body) else {
tracing::debug!("dropping a TSP message whose body is not a JSON object");
return None;
};
match verb {
b"r" => Some(Item::Reply(Reply::decode(map))),
b"e" => Some(Item::Event(Event::decode(map))),
_ => None,
}
}
fn parameter(segment: &[u8]) -> bool {
let Some(eq) = segment.iter().position(|&b| b == b'=') else {
return false;
};
let (key, value) = (&segment[..eq], &segment[eq + 1..]);
!key.is_empty()
&& key
.iter()
.all(|&b| b.is_ascii_alphanumeric() || b == b'_' || b == b'-')
&& value
.iter()
.all(|&b| (0x21..=0x7e).contains(&b) && b != b';')
}