use crate::charset::Charset;
use crate::color::Color;
use std::fmt::Display;
const PARAMS_LEN: usize = 32;
#[derive(Debug, Default)]
pub struct Parser {
pub state: State,
params: [Param; PARAMS_LEN],
cur_param: usize,
intermediate: Option<char>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub enum State {
#[default]
Ground,
Escape,
EscapeIntermediate,
CsiEntry,
CsiParam,
CsiIntermediate,
CsiIgnore,
DcsEntry,
DcsParam,
DcsIntermediate,
DcsPassthrough,
DcsIgnore,
OscString,
SosPmApcString,
}
#[derive(Debug, PartialEq)]
pub enum Function {
Bs,
Cbt(u16),
Cha(u16),
Cht(u16),
Cnl(u16),
Cpl(u16),
Cr,
Ctc(CtcOp),
Cub(u16),
Cud(u16),
Cuf(u16),
Cup(u16, u16),
Cuu(u16),
Dch(u16),
Decaln,
Decrc,
Decrst(DecModes),
Decsc,
Decset(DecModes),
Decstbm(u16, u16),
Decstr,
Dl(u16),
Ech(u16),
Ed(EdScope),
El(ElScope),
G1d4(Charset),
Gzd4(Charset),
Ht,
Hts,
Ich(u16),
Il(u16),
Lf,
Nel,
Print(char),
Rep(u16),
Ri,
Ris,
Rm(AnsiModes),
Scorc,
Scosc,
Sd(u16),
Sgr(SgrOps),
Si,
Sm(AnsiModes),
So,
Su(u16),
Tbc(TbcScope),
Vpa(u16),
Vpr(u16),
Xtwinops(XtwinopsOp),
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[repr(u16)]
pub enum AnsiMode {
Insert = 4, NewLine = 20, }
#[derive(Debug, PartialEq)]
pub enum CtcOp {
Set,
ClearCurrentColumn,
ClearAll,
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[repr(u16)]
pub enum DecMode {
CursorKeys = 1, Origin = 6, AutoWrap = 7, TextCursorEnable = 25, AltScreenBuffer = 1047, SaveCursor = 1048, SaveCursorAltScreenBuffer = 1049, }
#[derive(Debug, PartialEq)]
pub enum EdScope {
Below,
Above,
All,
SavedLines,
}
#[derive(Debug, PartialEq)]
pub enum ElScope {
ToRight,
ToLeft,
All,
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum SgrOp {
Reset, SetBoldIntensity, SetFaintIntensity, SetItalic, SetUnderline, SetBlink, SetInverse, SetStrikethrough, ResetIntensity, ResetItalic, ResetUnderline, ResetBlink, ResetInverse, ResetStrikethrough, SetForegroundColor(Color), ResetForegroundColor, SetBackgroundColor(Color), ResetBackgroundColor, }
#[derive(Debug, PartialEq)]
pub enum TbcScope {
CurrentColumn,
All,
}
#[derive(Debug, PartialEq)]
pub enum XtwinopsOp {
Resize(u16, u16),
}
pub const SGR_OPS_INLINE_CAP: usize = 4;
#[derive(Debug, Clone, PartialEq)]
pub struct SgrOps(SgrOpsStorage);
#[derive(Debug, Clone, PartialEq)]
enum SgrOpsStorage {
Inline {
ops: [SgrOp; SGR_OPS_INLINE_CAP],
len: u8,
},
Heap(Vec<SgrOp>),
}
impl SgrOps {
pub(crate) fn new() -> Self {
Self(SgrOpsStorage::Inline {
ops: [SgrOp::Reset; SGR_OPS_INLINE_CAP],
len: 0,
})
}
pub(crate) fn collect<I: IntoIterator<Item = SgrOp>>(iter: I) -> Self {
let mut ops = Self::new();
for op in iter {
ops.push(op);
}
ops
}
pub(crate) fn len(&self) -> usize {
match &self.0 {
SgrOpsStorage::Inline { len, .. } => *len as usize,
SgrOpsStorage::Heap(v) => v.len(),
}
}
pub(crate) fn is_empty(&self) -> bool {
self.len() == 0
}
pub(crate) fn as_slice(&self) -> &[SgrOp] {
match &self.0 {
SgrOpsStorage::Inline { ops, len } => &ops[..*len as usize],
SgrOpsStorage::Heap(v) => v.as_slice(),
}
}
pub(crate) fn push(&mut self, op: SgrOp) {
match &mut self.0 {
SgrOpsStorage::Inline { ops, len } => {
if (*len as usize) < SGR_OPS_INLINE_CAP {
ops[*len as usize] = op;
*len += 1;
} else {
let mut v = Vec::with_capacity(SGR_OPS_INLINE_CAP * 2);
v.extend_from_slice(ops);
v.push(op);
self.0 = SgrOpsStorage::Heap(v);
}
}
SgrOpsStorage::Heap(v) => v.push(op),
}
}
}
impl From<Vec<SgrOp>> for SgrOps {
fn from(v: Vec<SgrOp>) -> Self {
if v.len() <= SGR_OPS_INLINE_CAP {
Self::collect(v)
} else {
Self(SgrOpsStorage::Heap(v))
}
}
}
impl From<&[SgrOp]> for SgrOps {
fn from(v: &[SgrOp]) -> Self {
Self::collect(v.iter().copied())
}
}
pub const MODES_INLINE_CAP: usize = 4;
#[derive(Debug, Clone, PartialEq)]
pub struct AnsiModes(AnsiModesStorage);
#[derive(Debug, Clone, PartialEq)]
enum AnsiModesStorage {
Inline {
modes: [AnsiMode; MODES_INLINE_CAP],
len: u8,
},
Heap(Vec<AnsiMode>),
}
impl AnsiModes {
pub(crate) fn new() -> Self {
Self(AnsiModesStorage::Inline {
modes: [AnsiMode::Insert; MODES_INLINE_CAP],
len: 0,
})
}
pub(crate) fn one(mode: AnsiMode) -> Self {
let mut modes = Self::new();
modes.push(mode);
modes
}
pub(crate) fn collect<I: IntoIterator<Item = AnsiMode>>(iter: I) -> Self {
let mut v = Self::new();
for m in iter {
v.push(m);
}
v
}
pub(crate) fn as_slice(&self) -> &[AnsiMode] {
match &self.0 {
AnsiModesStorage::Inline { modes, len } => &modes[..*len as usize],
AnsiModesStorage::Heap(v) => v.as_slice(),
}
}
pub(crate) fn push(&mut self, m: AnsiMode) {
match &mut self.0 {
AnsiModesStorage::Inline { modes, len } => {
if (*len as usize) < MODES_INLINE_CAP {
modes[*len as usize] = m;
*len += 1;
} else {
let mut v = Vec::with_capacity(MODES_INLINE_CAP * 2);
v.extend_from_slice(modes);
v.push(m);
self.0 = AnsiModesStorage::Heap(v);
}
}
AnsiModesStorage::Heap(v) => v.push(m),
}
}
}
impl From<Vec<AnsiMode>> for AnsiModes {
fn from(v: Vec<AnsiMode>) -> Self {
if v.len() <= MODES_INLINE_CAP {
Self::collect(v)
} else {
Self(AnsiModesStorage::Heap(v))
}
}
}
impl From<&[AnsiMode]> for AnsiModes {
fn from(v: &[AnsiMode]) -> Self {
Self::collect(v.iter().copied())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct DecModes(DecModesStorage);
#[derive(Debug, Clone, PartialEq)]
enum DecModesStorage {
Inline {
modes: [DecMode; MODES_INLINE_CAP],
len: u8,
},
Heap(Vec<DecMode>),
}
impl DecModes {
pub(crate) fn new() -> Self {
Self(DecModesStorage::Inline {
modes: [DecMode::CursorKeys; MODES_INLINE_CAP],
len: 0,
})
}
pub(crate) fn one(mode: DecMode) -> Self {
let mut modes = Self::new();
modes.push(mode);
modes
}
pub(crate) fn collect<I: IntoIterator<Item = DecMode>>(iter: I) -> Self {
let mut v = Self::new();
for m in iter {
v.push(m);
}
v
}
pub(crate) fn as_slice(&self) -> &[DecMode] {
match &self.0 {
DecModesStorage::Inline { modes, len } => &modes[..*len as usize],
DecModesStorage::Heap(v) => v.as_slice(),
}
}
pub(crate) fn push(&mut self, m: DecMode) {
match &mut self.0 {
DecModesStorage::Inline { modes, len } => {
if (*len as usize) < MODES_INLINE_CAP {
modes[*len as usize] = m;
*len += 1;
} else {
let mut v = Vec::with_capacity(MODES_INLINE_CAP * 2);
v.extend_from_slice(modes);
v.push(m);
self.0 = DecModesStorage::Heap(v);
}
}
DecModesStorage::Heap(v) => v.push(m),
}
}
}
impl From<Vec<DecMode>> for DecModes {
fn from(v: Vec<DecMode>) -> Self {
if v.len() <= MODES_INLINE_CAP {
Self::collect(v)
} else {
Self(DecModesStorage::Heap(v))
}
}
}
impl From<&[DecMode]> for DecModes {
fn from(v: &[DecMode]) -> Self {
Self::collect(v.iter().copied())
}
}
impl Parser {
pub fn new() -> Self {
Default::default()
}
pub fn feed(&mut self, input: char) -> Option<Function> {
use State::*;
let input2 = if input >= '\u{a0}' { '\u{41}' } else { input };
match (&self.state, input2) {
(Ground, '\u{20}'..='\u{7f}') => {
return Some(Function::Print(input));
}
(CsiParam, '\u{30}'..='\u{3b}') => {
self.param(input);
}
(_, '\u{1b}') => {
self.state = Escape;
self.clear();
}
(Escape, '\u{5b}') => {
self.state = CsiEntry;
self.clear();
}
(CsiParam | CsiEntry | CsiIntermediate, '\u{40}'..='\u{7e}') => {
self.state = Ground;
return self.csi_dispatch(input);
}
(CsiEntry, '\u{30}'..='\u{39}') | (CsiEntry, '\u{3b}') => {
self.state = CsiParam;
self.param(input);
}
(Ground, '\u{00}'..='\u{17}') | (Ground, '\u{19}') | (Ground, '\u{1c}'..='\u{1f}') => {
return self.execute(input);
}
(OscString, '\u{20}'..='\u{7f}') => {
self.osc_put(input);
}
(Escape, '\u{20}'..='\u{2f}') => {
self.state = EscapeIntermediate;
self.collect(input);
}
(EscapeIntermediate, '\u{30}'..='\u{7e}')
| (Escape, '\u{30}'..='\u{4f}')
| (Escape, '\u{51}'..='\u{57}')
| (Escape, '\u{59}')
| (Escape, '\u{5a}')
| (Escape, '\u{5c}')
| (Escape, '\u{60}'..='\u{7e}') => {
self.state = Ground;
return self.esc_dispatch(input);
}
(CsiEntry, '\u{3c}'..='\u{3f}') => {
self.state = CsiParam;
self.collect(input);
}
(DcsPassthrough, '\u{00}'..='\u{17}')
| (DcsPassthrough, '\u{19}')
| (DcsPassthrough, '\u{1c}'..='\u{7e}') => {
self.put(input);
}
(CsiIgnore, '\u{40}'..='\u{7e}') => {
self.state = Ground;
}
(CsiParam, '\u{3c}'..='\u{3f}')
| (CsiIntermediate, '\u{30}'..='\u{3f}')
| (CsiEntry, '\u{3a}') => {
self.state = CsiIgnore;
}
(Escape, '\u{5d}') => {
self.state = OscString;
}
(OscString, '\u{07}') => {
self.state = Ground;
}
(_, '\u{18}')
| (_, '\u{1a}')
| (_, '\u{80}'..='\u{8f}')
| (_, '\u{91}'..='\u{97}')
| (_, '\u{99}')
| (_, '\u{9a}') => {
self.state = Ground;
return self.execute(input);
}
(Escape, '\u{50}') => {
self.state = DcsEntry;
self.clear();
}
(CsiParam | CsiEntry, '\u{20}'..='\u{2f}') => {
self.state = CsiIntermediate;
self.collect(input);
}
(DcsParam, '\u{30}'..='\u{39}') | (DcsParam, '\u{3b}') => {
self.param(input);
}
(DcsEntry, '\u{3c}'..='\u{3f}') => {
self.state = DcsParam;
self.collect(input);
}
(DcsEntry | DcsParam | DcsIntermediate, '\u{40}'..='\u{7e}') => {
self.state = DcsPassthrough;
}
(DcsEntry | DcsParam, '\u{20}'..='\u{2f}') => {
self.state = DcsIntermediate;
self.collect(input);
}
(CsiIntermediate | EscapeIntermediate | DcsIntermediate, '\u{20}'..='\u{2f}') => {
self.collect(input);
}
(DcsEntry, '\u{3a}')
| (DcsIntermediate, '\u{30}'..='\u{3f}')
| (DcsParam, '\u{3a}')
| (DcsParam, '\u{3c}'..='\u{3f}') => {
self.state = DcsIgnore;
}
(DcsEntry, '\u{30}'..='\u{39}') | (DcsEntry, '\u{3b}') => {
self.state = DcsParam;
self.param(input);
}
(Escape | EscapeIntermediate
| CsiEntry | CsiParam | CsiIntermediate | CsiIgnore,
'\u{00}'..='\u{17}')
| (Escape | EscapeIntermediate
| CsiEntry | CsiParam | CsiIntermediate | CsiIgnore,
'\u{19}')
| (Escape | EscapeIntermediate
| CsiEntry | CsiParam | CsiIntermediate | CsiIgnore,
'\u{1c}'..='\u{1f}') => {
return self.execute(input);
}
(Escape, '\u{58}') | (Escape, '\u{5e}') | (Escape, '\u{5f}') => {
self.state = SosPmApcString;
}
(_, '\u{98}') | (_, '\u{9e}') | (_, '\u{9f}') => {
self.state = SosPmApcString;
}
(_, '\u{9c}') => {
self.state = Ground;
}
(_, '\u{9d}') => {
self.state = OscString;
}
(_, '\u{90}') => {
self.state = DcsEntry;
self.clear();
}
(_, '\u{9b}') => {
self.state = CsiEntry;
self.clear();
}
(Escape | EscapeIntermediate
| CsiEntry | CsiParam | CsiIntermediate | CsiIgnore
| DcsEntry | DcsParam | DcsIntermediate | DcsPassthrough | DcsIgnore
| SosPmApcString, '\u{7f}')
| (CsiIgnore, '\u{20}'..='\u{3f}')
| (DcsEntry | DcsParam | DcsIntermediate, '\u{00}'..='\u{17}')
| (DcsEntry | DcsParam | DcsIntermediate, '\u{19}')
| (DcsEntry | DcsParam | DcsIntermediate, '\u{1c}'..='\u{1f}')
| (DcsIgnore | SosPmApcString, '\u{00}'..='\u{17}')
| (DcsIgnore | SosPmApcString, '\u{19}')
| (DcsIgnore | SosPmApcString, '\u{1c}'..='\u{7e}')
| (OscString, '\u{00}'..='\u{06}')
| (OscString, '\u{08}'..='\u{17}')
| (OscString, '\u{19}')
| (OscString, '\u{1c}'..='\u{1f}') => {}
(Ground | Escape | EscapeIntermediate
| CsiEntry | CsiParam | CsiIntermediate | CsiIgnore
| DcsEntry | DcsParam | DcsIntermediate | DcsPassthrough | DcsIgnore
| OscString | SosPmApcString, '\u{a0}'..='\u{10ffff}') => {
unreachable!()
}
}
None
}
fn execute(&mut self, input: char) -> Option<Function> {
use Function::*;
match input {
'\u{08}' => Some(Bs),
'\u{09}' => Some(Ht),
'\u{0a}' => Some(Lf),
'\u{0b}' => Some(Lf),
'\u{0c}' => Some(Lf),
'\u{0d}' => Some(Cr),
'\u{0e}' => Some(So),
'\u{0f}' => Some(Si),
'\u{84}' => Some(Lf),
'\u{85}' => Some(Nel),
'\u{88}' => Some(Hts),
'\u{8d}' => Some(Ri),
_ => None,
}
}
fn clear(&mut self) {
for p in &mut self.params[..=self.cur_param] {
p.clear();
}
self.cur_param = 0;
self.intermediate = None;
}
fn collect(&mut self, input: char) {
self.intermediate = Some(input);
}
fn param(&mut self, input: char) {
if input == ';' {
self.cur_param += 1;
if self.cur_param == PARAMS_LEN {
self.cur_param = PARAMS_LEN - 1;
}
} else if input == ':' {
self.params[self.cur_param].add_part();
} else {
self.params[self.cur_param].add_digit((input as u8) - 0x30);
}
}
fn esc_dispatch(&mut self, input: char) -> Option<Function> {
use Function::*;
match (self.intermediate, input) {
(None, c) if ('@'..='_').contains(&c) => self.execute(((input as u8) + 0x40) as char),
(None, '7') => Some(Decsc),
(None, '8') => Some(Decrc),
(None, 'c') => {
self.state = State::Ground;
Some(Ris)
}
(Some('#'), '8') => Some(Decaln),
(Some('('), '0') => Some(Gzd4(Charset::Drawing)),
(Some('('), _) => Some(Gzd4(Charset::Ascii)),
(Some(')'), '0') => Some(G1d4(Charset::Drawing)),
(Some(')'), _) => Some(G1d4(Charset::Ascii)),
_ => None,
}
}
fn csi_dispatch(&mut self, input: char) -> Option<Function> {
use Function::*;
let ps = &self.params;
match (self.intermediate, input) {
(None, '@') => Some(Ich(ps[0].as_u16())),
(None, 'A') => Some(Cuu(ps[0].as_u16())),
(None, 'B') => Some(Cud(ps[0].as_u16())),
(None, 'C') => Some(Cuf(ps[0].as_u16())),
(None, 'D') => Some(Cub(ps[0].as_u16())),
(None, 'E') => Some(Cnl(ps[0].as_u16())),
(None, 'F') => Some(Cpl(ps[0].as_u16())),
(None, 'G') => Some(Cha(ps[0].as_u16())),
(None, 'H') => Some(Cup(ps[0].as_u16(), ps[1].as_u16())),
(None, 'I') => Some(Cht(ps[0].as_u16())),
(None, 'J') => match ps[0].as_u16() {
0 => Some(Ed(EdScope::Below)),
1 => Some(Ed(EdScope::Above)),
2 => Some(Ed(EdScope::All)),
3 => Some(Ed(EdScope::SavedLines)),
_ => None,
},
(None, 'K') => match ps[0].as_u16() {
0 => Some(El(ElScope::ToRight)),
1 => Some(El(ElScope::ToLeft)),
2 => Some(El(ElScope::All)),
_ => None,
},
(None, 'L') => Some(Il(ps[0].as_u16())),
(None, 'M') => Some(Dl(ps[0].as_u16())),
(None, 'P') => Some(Dch(ps[0].as_u16())),
(None, 'S') => Some(Su(ps[0].as_u16())),
(None, 'T') => Some(Sd(ps[0].as_u16())),
(None, 'W') => match ps[0].as_u16() {
0 => Some(Ctc(CtcOp::Set)),
2 => Some(Ctc(CtcOp::ClearCurrentColumn)),
5 => Some(Ctc(CtcOp::ClearAll)),
_ => None,
},
(None, 'X') => Some(Ech(ps[0].as_u16())),
(None, 'Z') => Some(Cbt(ps[0].as_u16())),
(None, '`') => Some(Cha(ps[0].as_u16())),
(None, 'a') => Some(Cuf(ps[0].as_u16())),
(None, 'b') => Some(Rep(ps[0].as_u16())),
(None, 'd') => Some(Vpa(ps[0].as_u16())),
(None, 'e') => Some(Vpr(ps[0].as_u16())),
(None, 'f') => Some(Cup(ps[0].as_u16(), ps[1].as_u16())),
(None, 'g') => match ps[0].as_u16() {
0 => Some(Tbc(TbcScope::CurrentColumn)),
3 => Some(Tbc(TbcScope::All)),
_ => None,
},
(None, 'h') => Some(Sm(AnsiModes::collect(
ps[..=self.cur_param].iter().filter_map(ansi_mode),
))),
(None, 'l') => Some(Rm(AnsiModes::collect(
ps[..=self.cur_param].iter().filter_map(ansi_mode),
))),
(None, 'm') => Some(Sgr(SgrOps::collect(SgrOpsDecoder {
ps: &ps[..=self.cur_param],
}))),
(None, 'r') => Some(Decstbm(ps[0].as_u16(), ps[1].as_u16())),
(None, 's') => Some(Scosc),
(None, 't') => {
if ps[0].as_u16() == 8 {
let rows = ps[1].as_u16();
let cols = ps[2].as_u16();
Some(Xtwinops(XtwinopsOp::Resize(cols, rows)))
} else {
None
}
}
(None, 'u') => Some(Scorc),
(Some('!'), 'p') => Some(Decstr),
(Some('?'), 'h') => Some(Decset(DecModes::collect(
ps[..=self.cur_param].iter().filter_map(dec_mode),
))),
(Some('?'), 'l') => Some(Decrst(DecModes::collect(
ps[..=self.cur_param].iter().filter_map(dec_mode),
))),
_ => None,
}
}
fn put(&mut self, _input: char) {}
fn osc_put(&mut self, _input: char) {}
pub(crate) fn dump(&self) -> String {
use State::*;
let mut seq = String::new();
match self.state {
Ground => {}
Escape => {
seq.push('\u{1b}');
}
EscapeIntermediate => {
let intermediates = self.intermediate.iter().collect::<String>();
let s = format!("\u{1b}{intermediates}");
seq.push_str(&s);
}
CsiEntry => {
seq.push('\u{9b}');
}
CsiParam => {
let intermediates = self.intermediate.iter().collect::<String>();
let params = &self.params[..=self.cur_param]
.iter()
.map(|param| param.to_string())
.collect::<Vec<_>>()
.join(";");
let s = &format!("\u{9b}{intermediates}{params}");
seq.push_str(s);
}
CsiIntermediate => {
let intermediates = self.intermediate.iter().collect::<String>();
let s = &format!("\u{9b}{intermediates}");
seq.push_str(s);
}
CsiIgnore => {
seq.push_str("\u{9b}\u{3a}");
}
DcsEntry => {
seq.push('\u{90}');
}
DcsIntermediate => {
let intermediates = self.intermediate.iter().collect::<String>();
let s = &format!("\u{90}{intermediates}");
seq.push_str(s);
}
DcsParam => {
let intermediates = self.intermediate.iter().collect::<String>();
let params = &self.params[..=self.cur_param]
.iter()
.map(|param| param.to_string())
.collect::<Vec<_>>()
.join(";");
let s = &format!("\u{90}{intermediates}{params}");
seq.push_str(s);
}
DcsPassthrough => {
let intermediates = self.intermediate.iter().collect::<String>();
let s = &format!("\u{90}{intermediates}\u{40}");
seq.push_str(s);
}
DcsIgnore => {
seq.push_str("\u{90}\u{3a}");
}
OscString => {
seq.push('\u{9d}');
}
SosPmApcString => {
seq.push('\u{98}');
}
}
seq
}
#[cfg(test)]
pub fn assert_eq(&self, other: &Parser) {
use State::*;
assert_eq!(self.state, other.state);
if self.state == CsiParam || self.state == DcsParam {
assert_eq!(self.params, other.params);
}
if self.state == EscapeIntermediate
|| self.state == CsiIntermediate
|| self.state == CsiParam
|| self.state == DcsIntermediate
|| self.state == DcsParam
{
assert_eq!(self.intermediate, other.intermediate);
}
}
}
pub(crate) fn dump(funs: &[Function]) -> String {
let mut seq = String::new();
for fun in funs {
dump_function(&mut seq, fun);
}
seq
}
pub(crate) fn dump_sgr_color(color: Color, base: u8) -> String {
match color {
Color::Indexed(c) if c < 8 => (base + c).to_string(),
Color::Indexed(c) if c < 16 => (base + 52 + c).to_string(),
Color::Indexed(c) => format!("{}:5:{}", base + 8, c),
Color::RGB(c) => format!("{}:2:{}:{}:{}", base + 8, c.r, c.g, c.b),
}
}
fn dump_function(seq: &mut String, fun: &Function) {
use AnsiMode::*;
use CtcOp::*;
use DecMode::*;
use EdScope::*;
use ElScope::*;
use Function::*;
use SgrOp::*;
use TbcScope::*;
use XtwinopsOp::*;
match fun {
Bs => seq.push('\u{08}'),
Cbt(n) => push_csi(seq, None, &[n.to_string()], 'Z'),
Cha(n) => push_csi(seq, None, &[n.to_string()], 'G'),
Cht(n) => push_csi(seq, None, &[n.to_string()], 'I'),
Cnl(n) => push_csi(seq, None, &[n.to_string()], 'E'),
Cpl(n) => push_csi(seq, None, &[n.to_string()], 'F'),
Cr => seq.push('\r'),
Ctc(op) => {
let param = match op {
Set => 0,
ClearCurrentColumn => 2,
ClearAll => 5,
};
push_csi(seq, None, &[param.to_string()], 'W');
}
Cub(n) => push_csi(seq, None, &[n.to_string()], 'D'),
Cud(n) => push_csi(seq, None, &[n.to_string()], 'B'),
Cuf(n) => push_csi(seq, None, &[n.to_string()], 'C'),
Cup(row, col) => push_csi(seq, None, &[row.to_string(), col.to_string()], 'H'),
Cuu(n) => push_csi(seq, None, &[n.to_string()], 'A'),
Dch(n) => push_csi(seq, None, &[n.to_string()], 'P'),
Decaln => push_esc(seq, Some('#'), '8'),
Decrc => push_esc(seq, None, '8'),
Decrst(modes) => {
let params = modes
.as_slice()
.iter()
.map(|mode| match mode {
CursorKeys => 1,
Origin => 6,
AutoWrap => 7,
TextCursorEnable => 25,
AltScreenBuffer => 1047,
SaveCursor => 1048,
SaveCursorAltScreenBuffer => 1049,
})
.map(|param| param.to_string())
.collect::<Vec<_>>();
push_csi(seq, Some('?'), ¶ms, 'l');
}
Decsc => push_esc(seq, None, '7'),
Decset(modes) => {
let params = modes
.as_slice()
.iter()
.map(|mode| match mode {
CursorKeys => 1,
Origin => 6,
AutoWrap => 7,
TextCursorEnable => 25,
AltScreenBuffer => 1047,
SaveCursor => 1048,
SaveCursorAltScreenBuffer => 1049,
})
.map(|param| param.to_string())
.collect::<Vec<_>>();
push_csi(seq, Some('?'), ¶ms, 'h');
}
Decstbm(top, bottom) => {
push_csi(seq, None, &[top.to_string(), bottom.to_string()], 'r');
}
Decstr => push_csi(seq, Some('!'), &[], 'p'),
Dl(n) => push_csi(seq, None, &[n.to_string()], 'M'),
Ech(n) => push_csi(seq, None, &[n.to_string()], 'X'),
Ed(scope) => {
let param = match scope {
Below => 0,
Above => 1,
EdScope::All => 2,
SavedLines => 3,
};
push_csi(seq, None, &[param.to_string()], 'J');
}
El(scope) => {
let param = match scope {
ToRight => 0,
ToLeft => 1,
ElScope::All => 2,
};
push_csi(seq, None, &[param.to_string()], 'K');
}
G1d4(charset) => push_esc(
seq,
Some(')'),
match charset {
Charset::Drawing => '0',
Charset::Ascii => 'B',
},
),
Gzd4(charset) => push_esc(
seq,
Some('('),
match charset {
Charset::Drawing => '0',
Charset::Ascii => 'B',
},
),
Ht => seq.push('\t'),
Hts => push_esc(seq, None, 'H'),
Ich(n) => push_csi(seq, None, &[n.to_string()], '@'),
Il(n) => push_csi(seq, None, &[n.to_string()], 'L'),
Lf => seq.push('\n'),
Nel => push_esc(seq, None, 'E'),
Print(ch) => seq.push(*ch),
Rep(n) => push_csi(seq, None, &[n.to_string()], 'b'),
Ri => push_esc(seq, None, 'M'),
Ris => push_esc(seq, None, 'c'),
Rm(modes) => {
let params = modes
.as_slice()
.iter()
.map(|mode| match mode {
Insert => 4,
NewLine => 20,
})
.map(|param| param.to_string())
.collect::<Vec<_>>();
push_csi(seq, None, ¶ms, 'l');
}
Scorc => push_csi(seq, None, &[], 'u'),
Scosc => push_csi(seq, None, &[], 's'),
Sd(n) => push_csi(seq, None, &[n.to_string()], 'T'),
Sgr(ops) => {
if ops.is_empty() {
seq.push_str("\x1b[38;2m");
} else {
let params = ops
.as_slice()
.iter()
.map(|op| match op {
Reset => "0".to_owned(),
SetBoldIntensity => "1".to_owned(),
SetFaintIntensity => "2".to_owned(),
SetItalic => "3".to_owned(),
SetUnderline => "4".to_owned(),
SetBlink => "5".to_owned(),
SetInverse => "7".to_owned(),
SetStrikethrough => "9".to_owned(),
ResetIntensity => "22".to_owned(),
ResetItalic => "23".to_owned(),
ResetUnderline => "24".to_owned(),
ResetBlink => "25".to_owned(),
ResetInverse => "27".to_owned(),
ResetStrikethrough => "29".to_owned(),
SetForegroundColor(color) => dump_sgr_color(*color, 30),
ResetForegroundColor => "39".to_owned(),
SetBackgroundColor(color) => dump_sgr_color(*color, 40),
ResetBackgroundColor => "49".to_owned(),
})
.collect::<Vec<_>>();
push_csi(seq, None, ¶ms, 'm');
}
}
Si => seq.push('\u{0f}'),
Sm(modes) => {
let params = modes
.as_slice()
.iter()
.map(|mode| match mode {
Insert => 4,
NewLine => 20,
})
.map(|param| param.to_string())
.collect::<Vec<_>>();
push_csi(seq, None, ¶ms, 'h');
}
So => seq.push('\u{0e}'),
Su(n) => push_csi(seq, None, &[n.to_string()], 'S'),
Tbc(scope) => {
let param = match scope {
CurrentColumn => 0,
TbcScope::All => 3,
};
push_csi(seq, None, &[param.to_string()], 'g');
}
Vpa(n) => push_csi(seq, None, &[n.to_string()], 'd'),
Vpr(n) => push_csi(seq, None, &[n.to_string()], 'e'),
Xtwinops(Resize(cols, rows)) => {
push_csi(
seq,
None,
&["8".to_owned(), rows.to_string(), cols.to_string()],
't',
);
}
}
}
fn push_esc(seq: &mut String, intermediate: Option<char>, final_char: char) {
seq.push('\u{1b}');
if let Some(intermediate) = intermediate {
seq.push(intermediate);
}
seq.push(final_char);
}
fn push_csi(seq: &mut String, intermediate: Option<char>, params: &[String], final_char: char) {
seq.push('\u{1b}');
seq.push('[');
if let Some(intermediate) = intermediate {
seq.push(intermediate);
}
if let Some((first, rest)) = params.split_first() {
seq.push_str(first);
for param in rest {
seq.push(';');
seq.push_str(param);
}
}
seq.push(final_char);
}
fn ansi_mode(param: &Param) -> Option<AnsiMode> {
use AnsiMode::*;
match param.as_u16() {
4 => Some(Insert),
20 => Some(NewLine),
_ => None,
}
}
struct SgrOpsDecoder<'a> {
ps: &'a [Param],
}
impl<'a> Iterator for SgrOpsDecoder<'a> {
type Item = SgrOp;
fn next(&mut self) -> Option<Self::Item> {
use SgrOp::*;
while let Some(param) = self.ps.first() {
match param.parts() {
[0] => {
self.ps = &self.ps[1..];
return Some(Reset);
}
[1] => {
self.ps = &self.ps[1..];
return Some(SetBoldIntensity);
}
[2] => {
self.ps = &self.ps[1..];
return Some(SetFaintIntensity);
}
[3] => {
self.ps = &self.ps[1..];
return Some(SetItalic);
}
[4] => {
self.ps = &self.ps[1..];
return Some(SetUnderline);
}
[5] => {
self.ps = &self.ps[1..];
return Some(SetBlink);
}
[7] => {
self.ps = &self.ps[1..];
return Some(SetInverse);
}
[9] => {
self.ps = &self.ps[1..];
return Some(SetStrikethrough);
}
[21] | [22] => {
self.ps = &self.ps[1..];
return Some(ResetIntensity);
}
[23] => {
self.ps = &self.ps[1..];
return Some(ResetItalic);
}
[24] => {
self.ps = &self.ps[1..];
return Some(ResetUnderline);
}
[25] => {
self.ps = &self.ps[1..];
return Some(ResetBlink);
}
[27] => {
self.ps = &self.ps[1..];
return Some(ResetInverse);
}
[29] => {
self.ps = &self.ps[1..];
return Some(ResetStrikethrough);
}
[param] if *param >= 30 && *param <= 37 => {
let color = Color::Indexed((param - 30) as u8);
self.ps = &self.ps[1..];
return Some(SetForegroundColor(color));
}
[38, 2, r, g, b] | [38, 2, _, r, g, b] => {
self.ps = &self.ps[1..];
return Some(SetForegroundColor(Color::rgb(*r as u8, *g as u8, *b as u8)));
}
[38, 5, idx] => {
let color = Color::Indexed(*idx as u8);
self.ps = &self.ps[1..];
return Some(SetForegroundColor(color));
}
[38] => match self.ps.get(1).map(|p| p.parts()) {
None => {
self.ps = &self.ps[1..];
}
Some([2]) => {
if let Some(b) = self.ps.get(4) {
let r = self.ps.get(2).unwrap().as_u16();
let g = self.ps.get(3).unwrap().as_u16();
let b = b.as_u16();
let color = Color::rgb(r as u8, g as u8, b as u8);
self.ps = &self.ps[5..];
return Some(SetForegroundColor(color));
} else {
self.ps = &self.ps[2..];
}
}
Some([5]) => {
if let Some(idx) = self.ps.get(2) {
let idx = idx.as_u16();
let color = Color::Indexed(idx as u8);
self.ps = &self.ps[3..];
return Some(SetForegroundColor(color));
} else {
self.ps = &self.ps[2..];
}
}
Some(_) => {
self.ps = &self.ps[1..];
}
},
[39] => {
self.ps = &self.ps[1..];
return Some(ResetForegroundColor);
}
[param] if *param >= 40 && *param <= 47 => {
let color = Color::Indexed((param - 40) as u8);
self.ps = &self.ps[1..];
return Some(SetBackgroundColor(color));
}
[48, 2, r, g, b] | [48, 2, _, r, g, b] => {
let color = Color::rgb(*r as u8, *g as u8, *b as u8);
self.ps = &self.ps[1..];
return Some(SetBackgroundColor(color));
}
[48, 5, idx] => {
let color = Color::Indexed(*idx as u8);
self.ps = &self.ps[1..];
return Some(SetBackgroundColor(color));
}
[48] => match self.ps.get(1).map(|p| p.parts()) {
None => {
self.ps = &self.ps[1..];
}
Some([2]) => {
if let Some(b) = self.ps.get(4) {
let r = self.ps.get(2).unwrap().as_u16();
let g = self.ps.get(3).unwrap().as_u16();
let b = b.as_u16();
let color = Color::rgb(r as u8, g as u8, b as u8);
self.ps = &self.ps[5..];
return Some(SetBackgroundColor(color));
} else {
self.ps = &self.ps[2..];
}
}
Some([5]) => {
if let Some(idx) = self.ps.get(2) {
let idx = idx.as_u16();
let color = Color::Indexed(idx as u8);
self.ps = &self.ps[3..];
return Some(SetBackgroundColor(color));
} else {
self.ps = &self.ps[2..];
}
}
Some(_) => {
self.ps = &self.ps[1..];
}
},
[49] => {
self.ps = &self.ps[1..];
return Some(ResetBackgroundColor);
}
[param] if *param >= 90 && *param <= 97 => {
let color = Color::Indexed((param - 90 + 8) as u8);
self.ps = &self.ps[1..];
return Some(SetForegroundColor(color));
}
[param] if *param >= 100 && *param <= 107 => {
let color = Color::Indexed((param - 100 + 8) as u8);
self.ps = &self.ps[1..];
return Some(SetBackgroundColor(color));
}
_ => {
self.ps = &self.ps[1..];
}
}
}
None
}
}
fn dec_mode(param: &Param) -> Option<DecMode> {
use DecMode::*;
match param.as_u16() {
1 => Some(CursorKeys),
6 => Some(Origin),
7 => Some(AutoWrap),
25 => Some(TextCursorEnable),
47 => Some(AltScreenBuffer), 1047 => Some(AltScreenBuffer),
1048 => Some(SaveCursor),
1049 => Some(SaveCursorAltScreenBuffer),
_ => None,
}
}
const MAX_PARAM_LEN: usize = 6;
#[derive(Debug, PartialEq, Clone)]
struct Param {
cur_part: usize,
pub parts: [u16; MAX_PARAM_LEN],
}
impl Param {
pub fn new(number: u16) -> Self {
Self {
cur_part: 0,
parts: [number, 0, 0, 0, 0, 0],
}
}
pub fn clear(&mut self) {
self.parts[..=self.cur_part].fill(0);
self.cur_part = 0;
}
pub fn add_part(&mut self) {
self.cur_part = (self.cur_part + 1).min(5);
}
pub fn add_digit(&mut self, input: u8) {
let number = &mut self.parts[self.cur_part];
*number = (10 * (*number as u32) + (input as u32)) as u16;
}
pub fn as_u16(&self) -> u16 {
self.parts[0]
}
pub fn parts(&self) -> &[u16] {
&self.parts[..=self.cur_part]
}
}
impl Display for Param {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.parts() {
[] => unreachable!(),
[part] => write!(f, "{}", part),
[first, rest @ ..] => {
write!(f, "{first}")?;
for part in rest {
write!(f, ":{part}")?;
}
Ok(())
}
}
}
}
impl Default for Param {
fn default() -> Self {
Self::new(0)
}
}
impl From<u16> for Param {
fn from(value: u16) -> Self {
Self::new(value)
}
}
impl From<Vec<u16>> for Param {
fn from(values: Vec<u16>) -> Self {
let mut parts = [0u16; MAX_PARAM_LEN];
let mut cur_part = 0;
for (i, v) in values.iter().take(MAX_PARAM_LEN).enumerate() {
cur_part = i;
parts[i] = *v;
}
Self { cur_part, parts }
}
}
impl PartialEq<u16> for Param {
fn eq(&self, other: &u16) -> bool {
self.parts[0] == *other
}
}
impl PartialEq<Vec<u16>> for Param {
fn eq(&self, other: &Vec<u16>) -> bool {
self.parts[..=self.cur_part] == other[..]
}
}
#[cfg(test)]
mod tests {
use super::AnsiMode;
use super::AnsiModes;
use super::CtcOp;
use super::DecMode;
use super::DecModes;
use super::EdScope;
use super::ElScope;
use super::Function;
use super::Function::*;
use super::Parser;
use super::SgrOp::*;
use super::SgrOps;
use super::State;
use super::TbcScope;
use super::XtwinopsOp;
use crate::charset::Charset;
use crate::color::Color;
use proptest::prelude::*;
fn parse(s: &str) -> Vec<Function> {
let mut parser = Parser::new();
s.chars().filter_map(|ch| parser.feed(ch)).collect()
}
fn emit(parser: &mut Parser, input: &[char]) -> Vec<Function> {
input.iter().filter_map(|ch| parser.feed(*ch)).collect()
}
fn feed(parser: &mut Parser, s: &str) {
for ch in s.chars() {
assert_eq!(parser.feed(ch), None);
}
}
fn sgr_ops<const N: usize>(ops: [super::SgrOp; N]) -> SgrOps {
SgrOps::from(&ops[..])
}
fn ansi_modes<const N: usize>(modes: [AnsiMode; N]) -> AnsiModes {
AnsiModes::from(&modes[..])
}
fn dec_modes<const N: usize>(modes: [DecMode; N]) -> DecModes {
DecModes::from(&modes[..])
}
fn assert_dump(input: &str, state: State, dump: &str) {
let mut parser = Parser::new();
feed(&mut parser, input);
assert_eq!(parser.state, state);
assert_eq!(parser.dump(), dump);
}
fn gen_parser_char() -> impl Strategy<Value = char> {
prop_oneof![
prop::sample::select(vec![
'\x1b', '\x18', '\x1a', '\u{9b}', '\u{9c}', '\u{9d}', '\u{90}', '\u{98}', '\u{9e}',
'\u{9f}', '[', ']', 'P', 'X', '^', '_', '?', '!', ';', ':', ' ', '#', '(', ')',
'@', 'A', 'B', 'C', 'D', 'H', 'J', 'K', 'L', 'M', 'P', 'S', 'T', 'W', 'X', 'Z',
'`', 'a', 'b', 'd', 'e', 'f', 'g', 'h', 'l', 'm', 'p', 'r', 's', 't', 'u', '0',
'1', '2', '3', '4', '5', '6', '7', '8', '9', '\x08', '\x09', '\x0a', '\x0d',
'\x0e', '\x0f',
]),
(0x20u8..=0x7eu8).prop_map(|b| b as char),
prop::sample::select(vec!['日', '▒', 'ハ']),
]
}
fn gen_parser_input(max_len: usize) -> impl Strategy<Value = Vec<char>> {
prop::collection::vec(gen_parser_char(), 0..=max_len)
}
fn gen_printable_text(max_len: usize) -> impl Strategy<Value = Vec<char>> {
prop::collection::vec(
prop_oneof![
(0x20u8..=0x7eu8).prop_map(|b| b as char),
prop::sample::select(vec!['日', '▒', 'ハ']),
],
0..=max_len,
)
}
fn gen_non_ground_prefix() -> impl Strategy<Value = Vec<char>> {
prop_oneof![
Just("\x1b".chars().collect()),
Just("\x1b[".chars().collect()),
Just("\x1b[12".chars().collect()),
Just("\x1b[1;".chars().collect()),
Just("\x1b[?".chars().collect()),
Just("\x1b[ ".chars().collect()),
Just("\x1b[:".chars().collect()),
Just("\x1b(".chars().collect()),
Just("\x1b]".chars().collect()),
Just("\x1b]title".chars().collect()),
Just("\x1bP".chars().collect()),
Just("\x1bP1;2".chars().collect()),
Just("\x1bP:".chars().collect()),
Just("\x1bX".chars().collect()),
Just("\x1bXabc".chars().collect()),
Just("\u{9b}".chars().collect()),
Just("\u{9b}12".chars().collect()),
Just("\u{9d}title".chars().collect()),
Just("\u{90}".chars().collect()),
Just("\u{90}1;2".chars().collect()),
Just("\u{98}".chars().collect()),
Just("\u{98}abc".chars().collect()),
]
}
#[test]
fn parse_c0() {
assert_eq!(parse("\x08"), [Bs]);
assert_eq!(parse("\x0a"), [Lf]);
assert_eq!(parse("\x0d"), [Cr]);
assert_eq!(parse("\x0e"), [So]);
assert_eq!(parse("\x0f"), [Si]);
}
#[test]
fn parse_c1() {
assert_eq!(parse("\u{84}"), [Lf]);
assert_eq!(parse("\u{85}"), [Nel]);
assert_eq!(parse("\u{88}"), [Hts]);
assert_eq!(parse("\u{8d}"), [Ri]);
}
#[test]
fn parse_esc_seq() {
assert_eq!(parse("\x1b7"), [Decsc]);
assert_eq!(parse("\x1b8"), [Decrc]);
assert_eq!(parse("\x1bc"), [Ris]);
assert_eq!(parse("\x1bM"), [Ri]);
assert_eq!(parse("\x1b#8"), [Decaln]);
assert_eq!(parse("\x1b(0"), [Gzd4(Charset::Drawing)]);
assert_eq!(parse("\x1b(B"), [Gzd4(Charset::Ascii)]);
assert_eq!(parse("\x1b)0"), [G1d4(Charset::Drawing)]);
assert_eq!(parse("\x1b)B"), [G1d4(Charset::Ascii)]);
}
#[test]
fn parse_csi_seq() {
assert_eq!(parse("\x1b[A"), [Cuu(0)]);
assert_eq!(parse("\x1b[B"), [Cud(0)]);
assert_eq!(parse("\x1b[2B"), [Cud(2)]);
assert_eq!(parse("\x1b[C"), [Cuf(0)]);
assert_eq!(parse("\x1b[3C"), [Cuf(3)]);
assert_eq!(parse("\x1b[D"), [Cub(0)]);
assert_eq!(parse("\x1b[4D"), [Cub(4)]);
assert_eq!(parse("\x1b[5E"), [Cnl(5)]);
assert_eq!(parse("\x1b[6F"), [Cpl(6)]);
assert_eq!(parse("\x1b[G"), [Cha(0)]);
assert_eq!(parse("\x1b[7G"), [Cha(7)]);
assert_eq!(parse("\x1b[H"), [Cup(0, 0)]);
assert_eq!(parse("\x1b[3;4H"), [Cup(3, 4)]);
assert_eq!(parse("\u{9b}3;4H"), [Cup(3, 4)]);
assert_eq!(parse("\x1b[8I"), [Cht(8)]);
assert_eq!(parse("\x1b[2Z"), [Cbt(2)]);
assert_eq!(parse("\x1b[`"), [Cha(0)]);
assert_eq!(parse("\x1b[9`"), [Cha(9)]);
assert_eq!(parse("\x1b[10a"), [Cuf(10)]);
assert_eq!(parse("\x1b[11b"), [Rep(11)]);
assert_eq!(parse("\x1b[12d"), [Vpa(12)]);
assert_eq!(parse("\x1b[13e"), [Vpr(13)]);
assert_eq!(parse("\x1b[f"), [Cup(0, 0)]);
assert_eq!(parse("\x1b[14;15f"), [Cup(14, 15)]);
assert_eq!(parse("\x1b[@"), [Ich(0)]);
assert_eq!(parse("\x1b[J"), [Ed(EdScope::Below)]);
assert_eq!(parse("\x1b[0J"), [Ed(EdScope::Below)]);
assert_eq!(parse("\x1b[1J"), [Ed(EdScope::Above)]);
assert_eq!(parse("\x1b[2J"), [Ed(EdScope::All)]);
assert_eq!(parse("\u{9b}2J"), [Ed(EdScope::All)]);
assert_eq!(parse("\x1b[3J"), [Ed(EdScope::SavedLines)]);
assert_eq!(parse("\x1b[K"), [El(ElScope::ToRight)]);
assert_eq!(parse("\x1b[0K"), [El(ElScope::ToRight)]);
assert_eq!(parse("\x1b[1K"), [El(ElScope::ToLeft)]);
assert_eq!(parse("\x1b[2K"), [El(ElScope::All)]);
assert_eq!(parse("\x1b[16L"), [Il(16)]);
assert_eq!(parse("\x1b[17M"), [Dl(17)]);
assert_eq!(parse("\x1b[18P"), [Dch(18)]);
assert_eq!(parse("\x1b[19S"), [Su(19)]);
assert_eq!(parse("\x1b[20T"), [Sd(20)]);
assert_eq!(parse("\x1b[W"), [Ctc(CtcOp::Set)]);
assert_eq!(parse("\x1b[2W"), [Ctc(CtcOp::ClearCurrentColumn)]);
assert_eq!(parse("\x1b[5W"), [Ctc(CtcOp::ClearAll)]);
assert_eq!(parse("\x1b[21X"), [Ech(21)]);
assert_eq!(parse("\x1b[g"), [Tbc(TbcScope::CurrentColumn)]);
assert_eq!(parse("\x1b[3g"), [Tbc(TbcScope::All)]);
assert_eq!(
parse("\x1b[4;20h"),
[Sm(ansi_modes([AnsiMode::Insert, AnsiMode::NewLine]))]
);
assert_eq!(
parse("\x1b[4;20l"),
[Rm(ansi_modes([AnsiMode::Insert, AnsiMode::NewLine]))]
);
assert_eq!(parse("\x1b[m"), [Sgr(sgr_ops([Reset]))]);
assert_eq!(parse("\x1b[2;5r"), [Decstbm(2, 5)]);
assert_eq!(
parse("\x1b[8;24;80t"),
[Xtwinops(XtwinopsOp::Resize(80, 24))]
);
assert_eq!(parse("\x1b[s"), [Scosc]);
assert_eq!(parse("\x1b[u"), [Scorc]);
assert_eq!(parse("\x1b[!p"), [Decstr]);
assert_eq!(parse("\x1b[?7h"), [Decset(dec_modes([DecMode::AutoWrap]))]);
assert_eq!(parse("\u{9b}?7h"), [Decset(dec_modes([DecMode::AutoWrap]))]);
assert_eq!(
parse("\x1b[?6;1047h"),
[Decset(dec_modes([
DecMode::Origin,
DecMode::AltScreenBuffer
]))]
);
assert_eq!(
parse("\x1b[?47h"),
[Decset(dec_modes([DecMode::AltScreenBuffer]))]
);
assert_eq!(
parse("\x1b[?1049h"),
[Decset(dec_modes([DecMode::SaveCursorAltScreenBuffer]))]
);
assert_eq!(
parse("\u{9b}?1049h"),
[Decset(dec_modes([DecMode::SaveCursorAltScreenBuffer]))]
);
assert_eq!(parse("\x1b[?7l"), [Decrst(dec_modes([DecMode::AutoWrap]))]);
assert_eq!(parse("\u{9b}?7l"), [Decrst(dec_modes([DecMode::AutoWrap]))]);
assert_eq!(
parse("\x1b[?47l"),
[Decrst(dec_modes([DecMode::AltScreenBuffer]))]
);
assert_eq!(
parse("\x1b[?6;1049l"),
[Decrst(dec_modes([
DecMode::Origin,
DecMode::SaveCursorAltScreenBuffer,
]))]
);
assert_eq!(
parse("\u{9b}?6;1049l"),
[Decrst(dec_modes([
DecMode::Origin,
DecMode::SaveCursorAltScreenBuffer,
]))]
);
}
#[test]
fn parse_partial_and_interrupted_seq() {
let mut parser = Parser::new();
assert_eq!(parser.feed('\x1b'), None);
assert_eq!(parser.feed('['), None);
assert_eq!(parser.feed('3'), None);
assert_eq!(parser.feed(';'), None);
assert_eq!(parser.feed('4'), None);
assert_eq!(parser.feed('H'), Some(Cup(3, 4)));
assert_eq!(parser.feed('\x1b'), None);
assert_eq!(parser.feed('['), None);
assert_eq!(parser.feed('3'), None);
assert_eq!(parser.feed('\x1b'), None);
assert_eq!(parser.feed('M'), Some(Ri));
feed(&mut parser, "\x1b");
assert_eq!(parser.state, State::Escape);
assert_eq!(parser.feed('\u{18}'), None);
assert_eq!(parser.state, State::Ground);
assert_eq!(parser.feed('A'), Some(Print('A')));
feed(&mut parser, "\x1b[12");
assert_eq!(parser.state, State::CsiParam);
assert_eq!(parser.feed('\u{1a}'), None);
assert_eq!(parser.state, State::Ground);
assert_eq!(parser.feed('B'), Some(Print('B')));
feed(&mut parser, "\x1b]title");
assert_eq!(parser.state, State::OscString);
assert_eq!(parser.feed('\u{18}'), None);
assert_eq!(parser.state, State::Ground);
assert_eq!(parser.feed('C'), Some(Print('C')));
feed(&mut parser, "\x1bP1;2");
assert_eq!(parser.state, State::DcsParam);
assert_eq!(parser.feed('\u{1a}'), None);
assert_eq!(parser.state, State::Ground);
assert_eq!(parser.feed('D'), Some(Print('D')));
}
#[test]
fn ignore_unsupported_seq() {
assert_eq!(parse("\x1b[4q"), []);
assert_eq!(parse("\x1b[9W"), []);
assert_eq!(parse("\x1b[?9999h"), [Decset(dec_modes([]))]);
assert_eq!(parse("\x1b[:m"), []);
assert_eq!(parse("\x1b[1?m"), []);
assert_eq!(parse("\x1b[ 1H"), []);
assert_eq!(parse("\x1b[ 1m"), []);
assert_eq!(parse("\x1b[1 q"), []);
assert_eq!(parse("\x1b[38;2m"), [Sgr(sgr_ops([]))]);
assert_eq!(parse("\x1b[48;5m"), [Sgr(sgr_ops([]))]);
}
#[test]
fn parse_sgr_seq() {
assert_eq!(
parse("\x1b[;1;m"),
[Sgr(sgr_ops([Reset, SetBoldIntensity, Reset]))]
);
assert_eq!(parse("\x1b[1m"), [Sgr(sgr_ops([SetBoldIntensity]))]);
assert_eq!(parse("\x1b[2m"), [Sgr(sgr_ops([SetFaintIntensity]))]);
assert_eq!(parse("\x1b[3m"), [Sgr(sgr_ops([SetItalic]))]);
assert_eq!(parse("\x1b[4m"), [Sgr(sgr_ops([SetUnderline]))]);
assert_eq!(parse("\x1b[5m"), [Sgr(sgr_ops([SetBlink]))]);
assert_eq!(parse("\x1b[7m"), [Sgr(sgr_ops([SetInverse]))]);
assert_eq!(parse("\x1b[9m"), [Sgr(sgr_ops([SetStrikethrough]))]);
assert_eq!(parse("\x1b[21m"), [Sgr(sgr_ops([ResetIntensity]))]);
assert_eq!(parse("\x1b[22m"), [Sgr(sgr_ops([ResetIntensity]))]);
assert_eq!(parse("\x1b[23m"), [Sgr(sgr_ops([ResetItalic]))]);
assert_eq!(parse("\x1b[24m"), [Sgr(sgr_ops([ResetUnderline]))]);
assert_eq!(parse("\x1b[25m"), [Sgr(sgr_ops([ResetBlink]))]);
assert_eq!(parse("\x1b[27m"), [Sgr(sgr_ops([ResetInverse]))]);
assert_eq!(parse("\x1b[29m"), [Sgr(sgr_ops([ResetStrikethrough]))]);
assert_eq!(
parse("\x1b[31m"),
[Sgr(sgr_ops([SetForegroundColor(Color::Indexed(1))]))]
);
assert_eq!(
parse("\x1b[38:2:1:2:3m"),
[Sgr(sgr_ops([SetForegroundColor(Color::rgb(1, 2, 3))]))]
);
assert_eq!(
parse("\x1b[38:2::1:2:3m"),
[Sgr(sgr_ops([SetForegroundColor(Color::rgb(1, 2, 3))]))]
);
assert_eq!(
parse("\x1b[38:5:88m"),
[Sgr(sgr_ops([SetForegroundColor(Color::Indexed(88))]))]
);
assert_eq!(parse("\x1b[39m"), [Sgr(sgr_ops([ResetForegroundColor]))]);
assert_eq!(
parse("\x1b[41m"),
[Sgr(sgr_ops([SetBackgroundColor(Color::Indexed(1))]))]
);
assert_eq!(
parse("\x1b[91m"),
[Sgr(sgr_ops([SetForegroundColor(Color::Indexed(9))]))]
);
assert_eq!(
parse("\x1b[48:2:1:2:3m"),
[Sgr(sgr_ops([SetBackgroundColor(Color::rgb(1, 2, 3))]))]
);
assert_eq!(
parse("\x1b[48:2::1:2:3m"),
[Sgr(sgr_ops([SetBackgroundColor(Color::rgb(1, 2, 3))]))]
);
assert_eq!(
parse("\x1b[48:5:99m"),
[Sgr(sgr_ops([SetBackgroundColor(Color::Indexed(99))]))]
);
assert_eq!(parse("\x1b[49m"), [Sgr(sgr_ops([ResetBackgroundColor]))]);
assert_eq!(
parse("\x1b[104m"),
[Sgr(sgr_ops([SetBackgroundColor(Color::Indexed(12))]))]
);
assert_eq!(
parse("\x1b[1;38;2;1;2;3;48;2;1;2;3;0m"),
[Sgr(sgr_ops([
SetBoldIntensity,
SetForegroundColor(Color::rgb(1, 2, 3)),
SetBackgroundColor(Color::rgb(1, 2, 3)),
Reset,
]))]
);
assert_eq!(
parse("\x1b[1;38;5;88;48;5;99;0m"),
[Sgr(sgr_ops([
SetBoldIntensity,
SetForegroundColor(Color::Indexed(88)),
SetBackgroundColor(Color::Indexed(99)),
Reset,
]))]
);
}
#[test]
fn parse_string_seq() {
assert_eq!(parse("\x1b]title\x07A"), [Print('A')]);
assert_eq!(parse("\x1b]title\x1b\\A"), [Print('A')]);
assert_eq!(parse("\u{9d}title\u{9c}A"), [Print('A')]);
assert_eq!(parse("\x1bPabc\u{9c}A"), [Print('A')]);
assert_eq!(parse("\x1bPabc\x1b\\A"), [Print('A')]);
assert_eq!(parse("\u{90}abc\u{9c}A"), [Print('A')]);
assert_eq!(parse("\x1bXabc\u{9c}A"), [Print('A')]);
assert_eq!(parse("\x1bXabc\x1b\\A"), [Print('A')]);
assert_eq!(parse("\x1b^abc\u{9c}A"), [Print('A')]);
assert_eq!(parse("\x1b^abc\x1b\\A"), [Print('A')]);
assert_eq!(parse("\x1b_abc\u{9c}A"), [Print('A')]);
assert_eq!(parse("\x1b_abc\x1b\\A"), [Print('A')]);
assert_eq!(parse("\u{98}abc\u{9c}A"), [Print('A')]);
assert_eq!(parse("\u{9e}abc\u{9c}A"), [Print('A')]);
assert_eq!(parse("\u{9f}abc\u{9c}A"), [Print('A')]);
}
#[test]
fn parse_unicode() {
assert_eq!(parse("日"), [Print('日')]);
assert_eq!(parse("\x1b[日A"), [Print('A')]);
}
#[test]
fn dump_non_ground_states() {
assert_dump("\x1b", State::Escape, "\x1b");
assert_dump("\x1b(", State::EscapeIntermediate, "\x1b(");
assert_dump("\x1b[", State::CsiEntry, "\u{9b}");
assert_dump("\x1b[ ", State::CsiIntermediate, "\u{9b} ");
assert_dump("\x1b[:", State::CsiIgnore, "\u{9b}\u{3a}");
assert_dump("\x1bP", State::DcsEntry, "\u{90}");
assert_dump("\x1bP ", State::DcsIntermediate, "\u{90} ");
assert_dump("\x1bP1;2", State::DcsParam, "\u{90}1;2");
assert_dump("\x1bPz", State::DcsPassthrough, "\u{90}\u{40}");
assert_dump("\x1bP:", State::DcsIgnore, "\u{90}\u{3a}");
assert_dump("\x1b]", State::OscString, "\u{9d}");
assert_dump("\x1bX", State::SosPmApcString, "\u{98}");
}
#[test]
fn dump() {
let mut parser = Parser::new();
for ch in "\x1b[;1;;38:2:1:2:3;".chars() {
parser.feed(ch);
}
assert_eq!(parser.dump(), "\u{9b}0;1;0;38:2:1:2:3;0");
}
#[test]
fn dump_functions_roundtrip() {
let functions = vec![
Bs,
Cbt(2),
Cha(7),
Cht(8),
Cnl(5),
Cpl(6),
Cr,
Ctc(CtcOp::Set),
Ctc(CtcOp::ClearCurrentColumn),
Ctc(CtcOp::ClearAll),
Cub(4),
Cud(2),
Cuf(3),
Cup(3, 4),
Cuu(1),
Dch(18),
Decaln,
Decrc,
Decrst(dec_modes([])),
Decrst(dec_modes([
DecMode::CursorKeys,
DecMode::Origin,
DecMode::AutoWrap,
DecMode::TextCursorEnable,
DecMode::AltScreenBuffer,
DecMode::SaveCursor,
DecMode::SaveCursorAltScreenBuffer,
])),
Decsc,
Decset(dec_modes([])),
Decset(dec_modes([
DecMode::CursorKeys,
DecMode::Origin,
DecMode::AutoWrap,
DecMode::TextCursorEnable,
DecMode::AltScreenBuffer,
DecMode::SaveCursor,
DecMode::SaveCursorAltScreenBuffer,
])),
Decstbm(2, 5),
Decstr,
Dl(17),
Ech(21),
Ed(EdScope::Below),
Ed(EdScope::Above),
Ed(EdScope::All),
Ed(EdScope::SavedLines),
El(ElScope::ToRight),
El(ElScope::ToLeft),
El(ElScope::All),
G1d4(Charset::Drawing),
G1d4(Charset::Ascii),
Gzd4(Charset::Drawing),
Gzd4(Charset::Ascii),
Ht,
Hts,
Ich(16),
Il(16),
Lf,
Nel,
Print('A'),
Print('日'),
Rep(11),
Ri,
Ris,
Rm(ansi_modes([])),
Rm(ansi_modes([AnsiMode::Insert, AnsiMode::NewLine])),
Scorc,
Scosc,
Sd(20),
Sgr(sgr_ops([])),
Sgr(sgr_ops([
Reset,
SetBoldIntensity,
SetFaintIntensity,
SetItalic,
SetUnderline,
SetBlink,
SetInverse,
SetStrikethrough,
ResetIntensity,
ResetItalic,
ResetUnderline,
ResetBlink,
ResetInverse,
ResetStrikethrough,
SetForegroundColor(Color::Indexed(1)),
ResetForegroundColor,
SetBackgroundColor(Color::rgb(1, 2, 3)),
ResetBackgroundColor,
])),
Si,
Sm(ansi_modes([])),
Sm(ansi_modes([AnsiMode::Insert, AnsiMode::NewLine])),
So,
Su(19),
Tbc(TbcScope::CurrentColumn),
Tbc(TbcScope::All),
Vpa(12),
Vpr(13),
Xtwinops(XtwinopsOp::Resize(80, 24)),
];
assert_eq!(parse(&super::dump(&functions)), functions);
}
proptest! {
#[test]
fn prop_dump_resume_equivalence(
input in gen_parser_input(64),
split in 0usize..65,
) {
let split = split.min(input.len());
let (prefix, suffix) = input.split_at(split);
let mut parser1 = Parser::new();
let _ = emit(&mut parser1, prefix);
let mut parser2 = Parser::new();
let dumped = parser1.dump();
let dump_output = dumped
.chars()
.filter_map(|ch| parser2.feed(ch))
.collect::<Vec<_>>();
prop_assert!(dump_output.is_empty());
parser1.assert_eq(&parser2);
let suffix_output1 = emit(&mut parser1, suffix);
let suffix_output2 = emit(&mut parser2, suffix);
prop_assert_eq!(suffix_output1, suffix_output2);
parser1.assert_eq(&parser2);
}
#[test]
fn prop_cancel_then_continue(
prefix in gen_non_ground_prefix(),
cancel in prop::sample::select(vec!['\x18', '\x1a']),
suffix in gen_printable_text(16),
) {
let mut parser = Parser::new();
let prefix_output = emit(&mut parser, &prefix);
prop_assert!(prefix_output.is_empty());
prop_assert_ne!(parser.state, State::Ground);
let cancel_output = parser.feed(cancel);
prop_assert_eq!(cancel_output, None);
prop_assert_eq!(parser.state, State::Ground);
let suffix_output = emit(&mut parser, &suffix);
let expected = suffix.iter().copied().map(Function::Print).collect::<Vec<_>>();
prop_assert_eq!(suffix_output, expected);
prop_assert_eq!(parser.state, State::Ground);
}
}
}