use std::sync::Arc;
use crate::host_profile::HostProfile;
use crate::screen::TerminalSize;
pub struct Parser {
parser: vte::Parser,
screen: crate::screen::Screen,
utf8_carry: [u8; 4],
utf8_carry_len: u8,
}
impl Parser {
#[must_use]
pub fn new(size: TerminalSize, scrollback_len: usize) -> Self {
Self {
parser: vte::Parser::new(),
screen: crate::screen::Screen::new(
crate::grid::Size {
rows: size.rows,
cols: size.cols,
},
scrollback_len,
),
utf8_carry: [0; 4],
utf8_carry_len: 0,
}
}
#[must_use]
pub fn with_profile(
size: TerminalSize,
scrollback_len: usize,
profile: Arc<HostProfile>,
) -> Self {
Self {
parser: vte::Parser::new(),
screen: crate::screen::Screen::with_profile(
crate::grid::Size {
rows: size.rows,
cols: size.cols,
},
scrollback_len,
profile,
),
utf8_carry: [0; 4],
utf8_carry_len: 0,
}
}
pub fn process(&mut self, bytes: &[u8]) {
let mut bytes = bytes;
let carry_len = usize::from(self.utf8_carry_len);
if carry_len > 0 {
let expected = utf8_expected_len(self.utf8_carry[0]);
let needed = expected - carry_len;
let consumable = bytes
.iter()
.take(needed)
.take_while(|&&b| (0x80..0xC0).contains(&b))
.count();
if consumable == needed {
self.utf8_carry[carry_len..expected].copy_from_slice(&bytes[..needed]);
self.parser.advance(
&mut crate::perform::PerformScreen::new(&mut self.screen),
&self.utf8_carry[..expected],
);
self.utf8_carry_len = 0;
bytes = &bytes[needed..];
} else if consumable == bytes.len() {
self.utf8_carry[carry_len..carry_len + consumable].copy_from_slice(bytes);
self.utf8_carry_len = (carry_len + consumable) as u8;
self.screen.flush_print_buffer();
return;
} else {
self.parser.advance(
&mut crate::perform::PerformScreen::new(&mut self.screen),
&self.utf8_carry[..carry_len],
);
self.utf8_carry_len = 0;
}
}
let hold = incomplete_utf8_tail_len(bytes);
let split_at = bytes.len() - hold;
self.parser.advance(
&mut crate::perform::PerformScreen::new(&mut self.screen),
&bytes[..split_at],
);
if hold > 0 {
self.utf8_carry[..hold].copy_from_slice(&bytes[split_at..]);
self.utf8_carry_len = hold as u8;
}
self.screen.flush_print_buffer();
}
#[must_use]
pub fn screen(&self) -> &crate::screen::Screen {
&self.screen
}
#[must_use]
pub fn screen_mut(&mut self) -> &mut crate::screen::Screen {
&mut self.screen
}
}
const fn utf8_expected_len(lead: u8) -> usize {
match lead {
0xC2..=0xDF => 2,
0xE0..=0xEF => 3,
0xF0..=0xF4 => 4,
_ => 1,
}
}
fn incomplete_utf8_tail_len(bytes: &[u8]) -> usize {
let mut conts = 0usize;
while conts < 3 && conts < bytes.len() {
let b = bytes[bytes.len() - 1 - conts];
if !(0x80..0xC0).contains(&b) {
break;
}
conts += 1;
}
if conts == bytes.len() {
return 0;
}
let lead = bytes[bytes.len() - 1 - conts];
let expected = match lead {
0xC2..=0xDF => 2,
0xE0..=0xEF => 3,
0xF0..=0xF4 => 4,
_ => return 0,
};
let have = conts + 1;
if have < expected { have } else { 0 }
}
impl std::io::Write for Parser {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.process(buf);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::Parser;
use crate::screen::TerminalSize;
#[test]
fn screen_set_size_restores_ascii_mapping() {
let mut parser = Parser::new(TerminalSize { rows: 24, cols: 80 }, 0);
parser.process(b"\x1b)0\x0e");
parser
.screen_mut()
.set_size(TerminalSize { rows: 30, cols: 80 });
parser.process(b"q");
assert_eq!(parser.screen().cell(0, 0).unwrap().contents(), "q");
}
}