use crate::screen::{DcsHandler, ScreenEvent};
const BEL: u8 = 7;
const BS: u8 = 8;
const HT: u8 = 9;
const LF: u8 = 10;
const VT: u8 = 11;
const FF: u8 = 12;
const CR: u8 = 13;
const SO: u8 = 14;
const SI: u8 = 15;
const DSR_OPERATING_STATUS: u16 = 5;
const DSR_CURSOR_POSITION: u16 = 6;
pub(crate) struct PerformScreen<'a> {
pub(crate) screen: &'a mut crate::screen::Screen,
}
impl<'a> PerformScreen<'a> {
pub(crate) fn new(screen: &'a mut crate::screen::Screen) -> Self {
Self { screen }
}
}
impl vte::Perform for PerformScreen<'_> {
fn print(&mut self, c: char) {
if c == '\u{fffd}' || ('\u{80}'..'\u{a0}').contains(&c) {
return;
}
if self.screen.grapheme_cluster_mode() {
self.screen.push_to_print_buffer(c);
} else {
self.screen.text(c);
}
}
fn execute(&mut self, b: u8) {
self.screen.flush_print_buffer();
self.screen.clear_last_printed();
match b {
BEL => self.screen.bell(),
BS => self.screen.bs(),
HT => self.screen.tab(),
LF => self.screen.lf(),
VT => self.screen.vt(),
FF => self.screen.ff(),
CR => self.screen.cr(),
SO => self.screen.so(),
SI => self.screen.si(),
_ => {}
}
}
fn esc_dispatch(&mut self, intermediates: &[u8], _ignore: bool, b: u8) {
self.screen.flush_print_buffer();
self.screen.clear_last_printed();
match intermediates {
[] => match b {
b'7' => self.screen.decsc(),
b'8' => self.screen.decrc(),
b'E' => self.screen.nel(),
b'H' => self.screen.hts(),
b'M' => self.screen.ri(),
b'c' => self.screen.ris(),
_ => {}
},
[b'#'] if b == b'8' => self.screen.decaln(),
[i @ (b'(' | b')' | b'*' | b'+')] => {
let slot = match i {
b'(' => 0,
b')' => 1,
b'*' => 2,
b'+' => 3,
_ => unreachable!(),
};
let charset = match b {
b'0' => crate::screen::Charset::LineDrawing,
_ => crate::screen::Charset::Ascii,
};
self.screen.designate_charset(slot, charset);
}
_ => {}
}
}
fn csi_dispatch(&mut self, params: &vte::Params, intermediates: &[u8], _ignore: bool, c: char) {
self.screen.flush_print_buffer();
if intermediates.is_empty() && c == 'b' {
self.screen.rep(canonicalize_params_1(params, 1));
return;
}
self.screen.clear_last_printed();
match intermediates.first() {
None => match c {
'@' => self.screen.ich(canonicalize_params_1(params, 1)),
'A' => self.screen.cuu(canonicalize_params_1(params, 1)),
'B' => self.screen.cud(canonicalize_params_1(params, 1)),
'C' => self.screen.cuf(canonicalize_params_1(params, 1)),
'D' => self.screen.cub(canonicalize_params_1(params, 1)),
'E' => self.screen.cnl(canonicalize_params_1(params, 1)),
'F' => self.screen.cpl(canonicalize_params_1(params, 1)),
'G' => self.screen.cha(canonicalize_params_1(params, 1)),
'H' => self.screen.cup(canonicalize_params_2(params, 1, 1)),
'I' => self.screen.cht(canonicalize_params_1(params, 1)),
'J' => self.screen.ed(canonicalize_params_1(params, 0)),
'K' => self.screen.el(canonicalize_params_1(params, 0)),
'L' => self.screen.il(canonicalize_params_1(params, 1)),
'M' => self.screen.dl(canonicalize_params_1(params, 1)),
'P' => self.screen.dch(canonicalize_params_1(params, 1)),
'S' => self.screen.su(canonicalize_params_1(params, 1)),
'T' => self.screen.sd(canonicalize_params_1(params, 1)),
'X' => self.screen.ech(canonicalize_params_1(params, 1)),
'Z' => self.screen.cbt(canonicalize_params_1(params, 1)),
'c' => {
let p = canonicalize_params_1(params, 0);
if p == 0 {
self.screen.pending_events.push(ScreenEvent::Da1);
}
}
'd' => self.screen.vpa(canonicalize_params_1(params, 1)),
'g' => self.screen.tbc(canonicalize_params_1(params, 0)),
'h' => self.screen.sm(params),
'l' => self.screen.rm(params),
'm' => self.screen.sgr(params),
'n' => {
let p = canonicalize_params_1(params, 0);
match p {
DSR_OPERATING_STATUS => {
self.screen.pending_events.push(ScreenEvent::DsrStatus);
}
DSR_CURSOR_POSITION => {
let crate::Position { row, col } = self.screen.cursor();
self.screen
.pending_events
.push(ScreenEvent::DsrCursorPosition {
row: row.saturating_add(1),
col: col.saturating_add(1),
});
}
_ => {}
}
}
'r' => self.screen.decstbm(canonicalize_params_decstbm(
params,
self.screen.grid().size(),
)),
't' => self.screen.xtwinops(params),
_ => {}
},
Some(b' ') if c == 'q' => {
self.screen.decscusr(canonicalize_params_1(params, 0));
}
Some(b'$') if c == 'p' => {
let mode = canonicalize_params_1(params, 0);
self.screen.ansi_mode_report(mode);
}
Some(b'<') if c == 'u' => {
let count = canonicalize_params_1(params, 1);
self.screen.kitty_pop(count);
}
Some(b'=') => match c {
'c' => {
self.screen.pending_events.push(ScreenEvent::Da3);
}
'u' => {
let (flags, mode) = canonicalize_params_2(params, 0, 1);
self.screen.kitty_set(flags as u8, mode);
}
_ => {}
},
Some(b'>') => match c {
'c' => {
self.screen.pending_events.push(ScreenEvent::Da2);
}
'q' => {
self.screen.pending_events.push(ScreenEvent::Xtversion);
}
'u' => {
let flags = canonicalize_params_1(params, 0);
self.screen.kitty_push(flags as u8);
}
_ => {}
},
Some(b'?') => match (intermediates.get(1), c) {
(None, 'J') => self.screen.ed(canonicalize_params_1(params, 0)),
(None, 'K') => self.screen.el(canonicalize_params_1(params, 0)),
(None, 'h') => self.screen.decset(params),
(None, 'l') => self.screen.decrst(params),
(None, 's') => self.screen.xtsave(params),
(None, 'r') => self.screen.xtrestore(params),
(None, 'u') => {
self.screen.kitty_query();
}
(Some(b'$'), 'p') => {
let mode = canonicalize_params_1(params, 0);
self.screen.decrqm(mode);
}
_ => {}
},
_ => {}
}
}
fn hook(&mut self, _params: &vte::Params, intermediates: &[u8], _ignore: bool, action: char) {
self.screen.flush_print_buffer();
self.screen.clear_last_printed();
self.screen.dcs_handler = match (intermediates, action) {
([b'$'], 'q') => DcsHandler::Decrqss(Vec::new()),
([b'+'], 'q') => DcsHandler::XtGetTcap(Vec::new()),
_ => DcsHandler::None,
};
}
fn put(&mut self, byte: u8) {
let cap = match self.screen.dcs_handler {
DcsHandler::Decrqss(_) => self.screen.host_profile.dcs_max_bytes_decrqss,
DcsHandler::XtGetTcap(_) => self.screen.host_profile.dcs_max_bytes_xtgettcap,
DcsHandler::None => return,
};
let buf = match &mut self.screen.dcs_handler {
DcsHandler::Decrqss(buf) | DcsHandler::XtGetTcap(buf) => buf,
DcsHandler::None => return,
};
if buf.len() >= cap {
self.screen.dcs_handler = DcsHandler::None;
return;
}
buf.push(byte);
}
fn unhook(&mut self) {
let handler = std::mem::take(&mut self.screen.dcs_handler);
match handler {
DcsHandler::Decrqss(query) => self.screen.decrqss(&query),
DcsHandler::XtGetTcap(data) => self.screen.xtgettcap(&data),
DcsHandler::None => {}
}
}
fn osc_dispatch(&mut self, params: &[&[u8]], _bel_terminated: bool) {
self.screen.flush_print_buffer();
self.screen.clear_last_printed();
self.screen.osc(params);
}
}
fn canonicalize_params_1(params: &vte::Params, default: u16) -> u16 {
let first = params.iter().next().map_or(0, |x| *x.first().unwrap_or(&0));
if first == 0 { default } else { first }
}
fn canonicalize_params_2(params: &vte::Params, default1: u16, default2: u16) -> (u16, u16) {
let mut iter = params.iter();
let first = iter.next().map_or(0, |x| *x.first().unwrap_or(&0));
let first = if first == 0 { default1 } else { first };
let second = iter.next().map_or(0, |x| *x.first().unwrap_or(&0));
let second = if second == 0 { default2 } else { second };
(first, second)
}
fn canonicalize_params_decstbm(params: &vte::Params, size: crate::grid::Size) -> (u16, u16) {
let mut iter = params.iter();
let top = iter.next().map_or(0, |x| *x.first().unwrap_or(&0));
let top = if top == 0 { 1 } else { top };
let bottom = iter.next().map_or(0, |x| *x.first().unwrap_or(&0));
let bottom = if bottom == 0 { size.rows } else { bottom };
(top, bottom)
}
#[cfg(test)]
mod tests {
use crate::{Position, TerminalSize};
fn parser() -> crate::Parser {
crate::Parser::new(TerminalSize { rows: 24, cols: 80 }, 0)
}
fn process(p: &mut crate::Parser, bytes: &[u8]) {
p.process(bytes);
}
fn drain_replies(p: &mut crate::Parser) -> Vec<u8> {
let host = p.screen().host_profile().clone();
p.screen_mut()
.drain_events()
.iter()
.filter_map(|e| crate::host_reply::auto_reply_bytes(e, &host))
.flatten()
.collect()
}
#[test]
fn c0_bell() {
use crate::screen::ScreenEvent;
let mut p = parser();
process(&mut p, b"\x07");
let events = p.screen_mut().drain_events();
assert!(events.contains(&ScreenEvent::Bell));
}
#[test]
fn c0_backspace_moves_cursor_left() {
let mut p = parser();
process(&mut p, b"AB\x08");
assert_eq!(p.screen().cursor(), Position { row: 0, col: 1 });
}
#[test]
fn c0_tab_advances_to_next_stop() {
let mut p = parser();
process(&mut p, b"A\t");
assert_eq!(p.screen().cursor(), Position { row: 0, col: 8 });
}
#[test]
fn c0_lf_moves_cursor_down() {
let mut p = parser();
process(&mut p, b"A\n");
assert_eq!(p.screen().cursor(), Position { row: 1, col: 1 });
}
#[test]
fn c0_cr_moves_cursor_to_col_zero() {
let mut p = parser();
process(&mut p, b"ABC\r");
assert_eq!(p.screen().cursor(), Position { row: 0, col: 0 });
}
#[test]
fn c0_so_activates_g1_charset() {
let mut p = parser();
process(&mut p, b"\x1b)0\x0e");
process(&mut p, b"q");
assert_eq!(p.screen().cell(0, 0).unwrap().contents(), "─");
}
#[test]
fn c0_si_restores_g0_charset() {
let mut p = parser();
process(&mut p, b"\x1b)0\x0e");
process(&mut p, b"\x0f"); process(&mut p, b"q");
assert_eq!(p.screen().cell(0, 0).unwrap().contents(), "q");
}
#[test]
fn esc_decsc_decrc_save_restore_cursor() {
let mut p = parser();
process(&mut p, b"\x1b[5;10H"); process(&mut p, b"\x1b7"); process(&mut p, b"\x1b[1;1H"); process(&mut p, b"\x1b8"); assert_eq!(p.screen().cursor(), Position { row: 4, col: 9 });
}
#[test]
fn esc_nel_moves_to_next_line_col_zero() {
let mut p = parser();
process(&mut p, b"ABC\x1bE");
assert_eq!(p.screen().cursor(), Position { row: 1, col: 0 });
}
#[test]
fn esc_hts_sets_tab_stop() {
let mut p = parser();
process(&mut p, b"\x1b[6G\x1bH\x1b[1G\t");
assert_eq!(p.screen().cursor(), Position { row: 0, col: 5 });
}
#[test]
fn esc_ri_reverse_index() {
let mut p = parser();
process(&mut p, b"\x1b[2;1H"); process(&mut p, b"\x1bM"); assert_eq!(p.screen().cursor(), Position { row: 0, col: 0 });
}
#[test]
fn esc_ris_full_reset() {
use crate::screen::ScreenEvent;
let mut p = parser();
process(&mut p, b"\x1b[5;10HABC");
process(&mut p, b"\x1bc"); assert_eq!(p.screen().cursor(), Position { row: 0, col: 0 });
let events = p.screen_mut().drain_events();
assert!(events.contains(&ScreenEvent::ScreenCleared));
}
#[test]
fn esc_decaln_fills_screen_with_e() {
let mut p = parser();
process(&mut p, b"\x1b#8");
assert_eq!(p.screen().cell(0, 0).unwrap().contents(), "E");
assert_eq!(p.screen().cell(23, 79).unwrap().contents(), "E");
}
#[test]
fn esc_charset_designation_g0_line_drawing() {
let mut p = parser();
process(&mut p, b"\x1b(0"); process(&mut p, b"q"); assert_eq!(p.screen().cell(0, 0).unwrap().contents(), "─");
}
#[test]
fn esc_charset_designation_g2_g3() {
let mut p = parser();
process(&mut p, b"\x1b*0"); process(&mut p, b"\x1b+0"); process(&mut p, b"q");
assert_eq!(p.screen().cell(0, 0).unwrap().contents(), "q");
}
#[test]
fn csi_cuu_cursor_up() {
let mut p = parser();
process(&mut p, b"\x1b[5;1H"); process(&mut p, b"\x1b[2A"); assert_eq!(p.screen().cursor(), Position { row: 2, col: 0 });
}
#[test]
fn csi_cud_cursor_down() {
let mut p = parser();
process(&mut p, b"\x1b[2B");
assert_eq!(p.screen().cursor(), Position { row: 2, col: 0 });
}
#[test]
fn csi_cuf_cursor_forward() {
let mut p = parser();
process(&mut p, b"\x1b[5C");
assert_eq!(p.screen().cursor(), Position { row: 0, col: 5 });
}
#[test]
fn csi_cub_cursor_backward() {
let mut p = parser();
process(&mut p, b"\x1b[10GABC\x1b[2D");
assert_eq!(p.screen().cursor(), Position { row: 0, col: 10 });
}
#[test]
fn csi_cup_absolute_position() {
let mut p = parser();
process(&mut p, b"\x1b[10;20H");
assert_eq!(p.screen().cursor(), Position { row: 9, col: 19 });
}
#[test]
fn csi_cup_defaults_to_origin() {
let mut p = parser();
process(&mut p, b"\x1b[5;5H"); process(&mut p, b"\x1b[H"); assert_eq!(p.screen().cursor(), Position { row: 0, col: 0 });
}
#[test]
fn csi_cha_horizontal_absolute() {
let mut p = parser();
process(&mut p, b"\x1b[20G");
assert_eq!(p.screen().cursor(), Position { row: 0, col: 19 });
}
#[test]
fn csi_vpa_vertical_absolute() {
let mut p = parser();
process(&mut p, b"\x1b[10d");
assert_eq!(p.screen().cursor(), Position { row: 9, col: 0 });
}
#[test]
fn csi_cnl_cursor_next_line() {
let mut p = parser();
process(&mut p, b"ABC\x1b[2E");
assert_eq!(p.screen().cursor(), Position { row: 2, col: 0 });
}
#[test]
fn csi_cpl_cursor_previous_line() {
let mut p = parser();
process(&mut p, b"\x1b[5;10H\x1b[2F");
assert_eq!(p.screen().cursor(), Position { row: 2, col: 0 });
}
#[test]
fn csi_ed_erase_display_mode_2() {
use crate::screen::ScreenEvent;
let mut p = parser();
process(&mut p, b"Hello\x1b[2J");
let events = p.screen_mut().drain_events();
assert!(events.contains(&ScreenEvent::ScreenCleared));
}
#[test]
fn csi_el_erase_line() {
let mut p = parser();
process(&mut p, b"Hello\x1b[1G\x1b[K");
assert!(!p.screen().cell(0, 0).unwrap().has_contents());
}
#[test]
fn csi_ich_insert_cells() {
let mut p = parser();
process(&mut p, b"AB\x1b[1G\x1b[@");
assert!(!p.screen().cell(0, 0).unwrap().has_contents());
assert_eq!(p.screen().cell(0, 1).unwrap().contents(), "A");
}
#[test]
fn csi_dch_delete_cells() {
let mut p = parser();
process(&mut p, b"ABC\x1b[1G\x1b[P");
assert_eq!(p.screen().cell(0, 0).unwrap().contents(), "B");
}
#[test]
fn csi_il_insert_lines() {
let mut p = parser();
process(&mut p, b"Line1\n\rLine2\x1b[1;1H\x1b[L");
assert!(!p.screen().cell(0, 0).unwrap().has_contents());
assert_eq!(p.screen().cell(1, 0).unwrap().contents(), "L");
}
#[test]
fn csi_dl_delete_lines() {
let mut p = parser();
process(&mut p, b"Line1\n\rLine2\x1b[1;1H\x1b[M");
assert_eq!(p.screen().cell(0, 0).unwrap().contents(), "L");
assert_eq!(p.screen().cell(0, 4).unwrap().contents(), "2");
}
#[test]
fn csi_rep_repeats_last_printable() {
let mut p = parser();
process(&mut p, b"-\x1b[5b");
for col in 0..6 {
assert_eq!(p.screen().cell(0, col).unwrap().contents(), "-");
}
assert!(!p.screen().cell(0, 6).unwrap().has_contents());
assert_eq!(p.screen().cursor(), Position { row: 0, col: 6 });
}
#[test]
fn csi_rep_after_cursor_movement_is_noop() {
let mut p = parser();
process(&mut p, b"-\x1b[10G\x1b[3b");
assert_eq!(p.screen().cell(0, 0).unwrap().contents(), "-");
for col in 1..20 {
assert!(!p.screen().cell(0, col).unwrap().has_contents());
}
assert_eq!(p.screen().cursor(), Position { row: 0, col: 9 });
}
#[test]
fn csi_rep_after_erase_is_noop() {
let mut p = parser();
process(&mut p, b"-\x1b[2K\x1b[3b");
for col in 0..10 {
assert!(!p.screen().cell(0, col).unwrap().has_contents());
}
}
#[test]
fn csi_rep_at_last_column_with_autowrap_wraps() {
let mut p = parser();
process(&mut p, b"\x1b[1;80H-\x1b[1b");
assert_eq!(p.screen().cell(0, 79).unwrap().contents(), "-");
assert_eq!(p.screen().cell(1, 0).unwrap().contents(), "-");
}
#[test]
fn csi_rep_at_last_column_no_autowrap_overwrites() {
let mut p = parser();
process(&mut p, b"\x1b[?7l\x1b[1;80H-\x1b[1b");
assert_eq!(p.screen().cell(0, 79).unwrap().contents(), "-");
for row in 1..24 {
assert!(!p.screen().cell(row, 0).unwrap().has_contents());
}
}
#[test]
fn csi_rep_repeats_wide_character() {
let mut p = parser();
let mut input = "ä¸".as_bytes().to_vec();
input.extend_from_slice(b"\x1b[2b");
process(&mut p, &input);
assert_eq!(p.screen().cell(0, 0).unwrap().contents(), "ä¸");
assert!(p.screen().cell(0, 1).unwrap().is_wide_continuation());
assert_eq!(p.screen().cell(0, 2).unwrap().contents(), "ä¸");
assert!(p.screen().cell(0, 3).unwrap().is_wide_continuation());
assert_eq!(p.screen().cell(0, 4).unwrap().contents(), "ä¸");
assert!(p.screen().cell(0, 5).unwrap().is_wide_continuation());
assert!(!p.screen().cell(0, 6).unwrap().has_contents());
}
#[test]
fn csi_rep_clamps_excessive_count() {
let mut p = parser();
process(&mut p, b"-\x1b[65535b");
assert_eq!(p.screen().cell(1, 0).unwrap().contents(), "-");
}
#[test]
fn csi_zero_param_uses_default() {
let mut p = parser();
process(&mut p, b"\x1b[3;1H\x1b[0A");
assert_eq!(p.screen().cursor(), Position { row: 1, col: 0 });
}
#[test]
fn csi_missing_param_uses_default() {
let mut p = parser();
process(&mut p, b"\x1b[3;1H\x1b[A");
assert_eq!(p.screen().cursor(), Position { row: 1, col: 0 });
}
#[test]
fn csi_cup_partial_params() {
let mut p = parser();
process(&mut p, b"\x1b[5H");
assert_eq!(p.screen().cursor(), Position { row: 4, col: 0 });
}
#[test]
fn da1_response() {
let mut p = parser();
process(&mut p, b"\x1b[c");
assert_eq!(drain_replies(&mut p), b"\x1b[?62;22;52c");
}
#[test]
fn da1_with_explicit_zero_param() {
let mut p = parser();
process(&mut p, b"\x1b[0c");
assert_eq!(drain_replies(&mut p), b"\x1b[?62;22;52c");
}
#[test]
fn da1_nonzero_param_suppressed() {
let mut p = parser();
process(&mut p, b"\x1b[1c");
assert!(drain_replies(&mut p).is_empty());
}
#[test]
fn da2_response() {
let mut p = parser();
process(&mut p, b"\x1b[>c");
assert_eq!(drain_replies(&mut p), b"\x1b[>0;0;0c");
}
#[test]
fn da3_response() {
let mut p = parser();
process(&mut p, b"\x1b[=c");
assert_eq!(drain_replies(&mut p), b"\x1bP!|746173747479\x1b\\");
}
#[test]
fn dsr_operating_status() {
let mut p = parser();
process(&mut p, b"\x1b[5n");
assert_eq!(drain_replies(&mut p), b"\x1b[0n");
}
#[test]
fn dsr_cursor_position_report() {
let mut p = parser();
process(&mut p, b"\x1b[5;10H\x1b[6n");
assert_eq!(drain_replies(&mut p), b"\x1b[5;10R");
}
#[test]
fn xtversion_response() {
let mut p = parser();
process(&mut p, b"\x1b[>q");
let resp = drain_replies(&mut p);
let resp_str = std::str::from_utf8(&resp).unwrap();
assert!(resp_str.starts_with("\x1bP>|tastty("));
assert!(resp_str.ends_with(")\x1b\\"));
}
#[test]
fn decscusr_sets_cursor_style() {
use crate::screen::{CursorStyle, ScreenEvent};
let mut p = parser();
process(&mut p, b"\x1b[2 q"); let events = p.screen_mut().drain_events();
assert!(events.contains(&ScreenEvent::CursorStyleChanged(CursorStyle::SteadyBlock)));
assert_eq!(p.screen().cursor_style(), CursorStyle::SteadyBlock);
}
#[test]
fn decset_bracketed_paste() {
use crate::screen::TerminalMode;
let mut p = parser();
assert!(!p.screen().mode(TerminalMode::BracketedPaste));
process(&mut p, b"\x1b[?2004h");
assert!(p.screen().mode(TerminalMode::BracketedPaste));
process(&mut p, b"\x1b[?2004l");
assert!(!p.screen().mode(TerminalMode::BracketedPaste));
}
#[test]
fn sm_insert_mode() {
let mut p = parser();
process(&mut p, b"\x1b[4h"); process(&mut p, b"AB\x1b[1G"); process(&mut p, b"X"); assert_eq!(p.screen().cell(0, 0).unwrap().contents(), "X");
assert_eq!(p.screen().cell(0, 1).unwrap().contents(), "A");
}
#[test]
fn kitty_push_pop_flags() {
let mut p = parser();
assert_eq!(p.screen().kitty_keyboard_flags(), 0);
process(&mut p, b"\x1b[>3u");
assert_eq!(p.screen().kitty_keyboard_flags(), 3);
process(&mut p, b"\x1b[<u");
assert_eq!(p.screen().kitty_keyboard_flags(), 0);
}
#[test]
fn kitty_set_flags() {
let mut p = parser();
process(&mut p, b"\x1b[>1u"); process(&mut p, b"\x1b[=5u"); assert_eq!(p.screen().kitty_keyboard_flags(), 5);
}
#[test]
fn kitty_query_flags() {
let mut p = parser();
process(&mut p, b"\x1b[>3u"); process(&mut p, b"\x1b[?u"); assert_eq!(drain_replies(&mut p), b"\x1b[?3u");
}
#[test]
fn dcs_decrqss_cursor_style() {
let mut p = parser();
process(&mut p, b"\x1b[2 q"); process(&mut p, b"\x1bP$q q\x1b\\");
let resp = drain_replies(&mut p);
let resp_str = std::str::from_utf8(&resp).unwrap();
assert!(resp_str.contains("$r2 q"), "got: {resp_str:?}");
}
#[test]
fn dcs_decrqss_sgr() {
let mut p = parser();
process(&mut p, b"\x1b[1m"); process(&mut p, b"\x1bP$qm\x1b\\");
let resp = drain_replies(&mut p);
let resp_str = std::str::from_utf8(&resp).unwrap();
assert!(resp_str.contains("$r1m"), "got: {resp_str:?}");
}
#[test]
fn dcs_xtgettcap_known_key() {
let mut p = parser();
process(&mut p, b"\x1bP+q544E\x1b\\");
let resp = drain_replies(&mut p);
let resp_str = std::str::from_utf8(&resp).unwrap();
assert!(
resp_str.contains("+r544E="),
"expected xtgettcap response, got: {resp_str:?}"
);
}
#[test]
fn dcs_xtgettcap_unknown_key() {
let mut p = parser();
process(&mut p, b"\x1bP+q5A5A5A5A\x1b\\");
let resp = drain_replies(&mut p);
let resp_str = std::str::from_utf8(&resp).unwrap();
assert!(
resp_str.contains("\x1bP0+r"),
"expected invalid response, got: {resp_str:?}"
);
}
#[test]
fn print_filters_replacement_char() {
let mut p = parser();
process(&mut p, "\u{fffd}".as_bytes());
assert!(!p.screen().cell(0, 0).unwrap().has_contents());
}
#[test]
fn print_filters_c1_control_range() {
let mut p = parser();
process(&mut p, "\u{0085}".as_bytes());
assert!(!p.screen().cell(0, 0).unwrap().has_contents());
}
#[test]
fn print_normal_ascii() {
let mut p = parser();
process(&mut p, b"A");
assert_eq!(p.screen().cell(0, 0).unwrap().contents(), "A");
assert_eq!(p.screen().cursor(), Position { row: 0, col: 1 });
}
#[test]
fn decstbm_sets_scroll_region() {
let mut p = parser();
process(&mut p, b"\x1b[5;10r");
process(&mut p, b"\x1b[10;1H\n");
assert_eq!(p.screen().cursor().row, 9);
}
#[test]
fn decstbm_default_params() {
let mut p = parser();
process(&mut p, b"\x1b[r");
process(&mut p, b"\x1b[24;1H\n");
assert_eq!(p.screen().cursor().row, 23);
}
}