use super::seq::SeqTracker;
use super::Emulator;
use crate::screen::{Cell, Color, Screen, Style};
pub(crate) struct Vt100Emulator {
parser: ::vt100::Parser,
tracker: SeqTracker,
}
impl Vt100Emulator {
pub(crate) fn new(rows: u16, cols: u16) -> Self {
Self {
parser: ::vt100::Parser::new(rows, cols, 0),
tracker: SeqTracker::new(),
}
}
}
impl Emulator for Vt100Emulator {
fn process(&mut self, bytes: &[u8]) {
self.tracker.feed(bytes);
self.parser.process(bytes);
}
fn snapshot(&self) -> Screen {
let screen = self.parser.screen();
let (rows, cols) = screen.size();
let mut cells = Vec::with_capacity(usize::from(rows) * usize::from(cols));
for row in 0..rows {
for col in 0..cols {
cells.push(screen.cell(row, col).map_or_else(
|| Cell::new(String::new(), Style::default(), false, false),
convert_cell,
));
}
}
let (cursor_row, cursor_col) = screen.cursor_position();
Screen::from_parts(
cols,
rows,
cursor_row,
cursor_col,
!screen.hide_cursor(),
cells,
)
}
fn mid_sequence(&self) -> bool {
self.tracker.mid_sequence()
}
fn set_size(&mut self, rows: u16, cols: u16) {
self.parser.screen_mut().set_size(rows, cols);
}
}
fn convert_cell(cell: &::vt100::Cell) -> Cell {
let style = Style {
fg: convert_color(cell.fgcolor()),
bg: convert_color(cell.bgcolor()),
bold: cell.bold(),
dim: cell.dim(),
italic: cell.italic(),
underline: cell.underline(),
reverse: cell.inverse(),
};
Cell::new(
cell.contents().to_owned(),
style,
cell.is_wide(),
cell.is_wide_continuation(),
)
}
fn convert_color(color: ::vt100::Color) -> Color {
match color {
::vt100::Color::Default => Color::Default,
::vt100::Color::Idx(i) => Color::Indexed(i),
::vt100::Color::Rgb(r, g, b) => Color::Rgb(r, g, b),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn emu_with(bytes: &[u8]) -> Vt100Emulator {
let mut emu = Vt100Emulator::new(4, 10);
emu.process(bytes);
emu
}
#[test]
fn renders_plain_text_into_the_grid() {
let emu = emu_with(b"hi\r\nthere");
let screen = emu.snapshot();
assert_eq!(screen.size(), (10, 4));
assert_eq!(screen.text(), "hi\nthere\n\n");
assert_eq!(screen.cursor(), (1, 5, true));
}
#[test]
fn positions_wide_characters_with_continuations() {
let emu = emu_with("汉x".as_bytes());
let screen = emu.snapshot();
let wide = screen.cell(0, 0).unwrap();
assert!(wide.is_wide());
assert_eq!(wide.contents(), "汉");
assert!(screen.cell(0, 1).unwrap().is_wide_continuation());
assert_eq!(screen.find("x"), Some((0, 2)));
}
#[test]
fn captures_sgr_styles() {
let emu = emu_with(b"\x1b[1;3;4;7;31mX\x1b[0m");
let screen = emu.snapshot();
let style = *screen.cell(0, 0).unwrap().style();
assert!(style.bold && style.italic && style.underline && style.reverse);
assert_eq!(style.fg, Color::Indexed(1));
assert_eq!(screen.cell(0, 1).unwrap().style(), &Style::default());
}
#[test]
fn hidden_cursor_is_reported() {
let emu = emu_with(b"\x1b[?25l");
assert_eq!(emu.snapshot().cursor(), (0, 0, false));
}
#[test]
fn mid_sequence_tracks_partial_escape() {
let mut emu = Vt100Emulator::new(4, 10);
emu.process(b"text\x1b[3");
assert!(emu.mid_sequence());
emu.process(b"1m");
assert!(!emu.mid_sequence());
}
#[test]
fn set_size_resizes_the_grid() {
let mut emu = emu_with(b"hello");
emu.set_size(2, 5);
let screen = emu.snapshot();
assert_eq!(screen.size(), (5, 2));
assert_eq!(screen.text(), "hello\n");
}
}