mirror of
https://github.com/wez/wezterm.git
synced 2024-12-23 21:32:13 +03:00
start building out unit tests
This commit is contained in:
parent
0eb4dd44b3
commit
a55395d7c9
@ -24,7 +24,7 @@ pub enum AnsiColor {
|
|||||||
White,
|
White,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
|
||||||
pub struct RgbColor {
|
pub struct RgbColor {
|
||||||
pub red: u8,
|
pub red: u8,
|
||||||
pub green: u8,
|
pub green: u8,
|
||||||
@ -33,7 +33,7 @@ pub struct RgbColor {
|
|||||||
|
|
||||||
impl RgbColor {}
|
impl RgbColor {}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
pub enum ColorAttribute {
|
pub enum ColorAttribute {
|
||||||
Foreground,
|
Foreground,
|
||||||
Background,
|
Background,
|
||||||
|
@ -10,11 +10,19 @@ pub mod color;
|
|||||||
mod csi;
|
mod csi;
|
||||||
use self::csi::*;
|
use self::csi::*;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test;
|
||||||
|
|
||||||
/// The response we given when queries for device attributes.
|
/// The response we given when queries for device attributes.
|
||||||
/// This particular string says "we are a VT102".
|
/// This particular string says "we are a VT102".
|
||||||
/// TODO: Consider VT220 extended response which can advertise
|
/// TODO: Consider VT220 extended response which can advertise
|
||||||
/// certain feature sets.
|
/// certain feature sets.
|
||||||
const DEVICE_IDENT: &[u8] = b"\x1b[?6c";
|
pub const DEVICE_IDENT: &[u8] = b"\x1b[?6c";
|
||||||
|
|
||||||
|
pub const CSI: &[u8] = b"\x1b[";
|
||||||
|
pub const OSC: &[u8] = b"\x1b]";
|
||||||
|
pub const ST: &[u8] = b"\x1b\\";
|
||||||
|
pub const DCS: &[u8] = b"\x1bP";
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@ -39,7 +47,7 @@ pub enum KeyCode {
|
|||||||
Shift,
|
Shift,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
pub struct CellAttributes {
|
pub struct CellAttributes {
|
||||||
attributes: u16,
|
attributes: u16,
|
||||||
pub foreground: color::ColorAttribute,
|
pub foreground: color::ColorAttribute,
|
||||||
@ -142,12 +150,18 @@ impl Default for CellAttributes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
pub struct Cell {
|
pub struct Cell {
|
||||||
chars: [u8; 8],
|
chars: [u8; 8],
|
||||||
pub attrs: CellAttributes,
|
pub attrs: CellAttributes,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Cell {
|
||||||
|
fn default() -> Cell {
|
||||||
|
Cell::from_char(' ', &CellAttributes::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Cell {
|
impl Cell {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn chars(&self) -> &[u8] {
|
pub fn chars(&self) -> &[u8] {
|
||||||
@ -168,6 +182,12 @@ impl Cell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<char> for Cell {
|
||||||
|
fn from(c: char) -> Cell {
|
||||||
|
Cell::from_char(c, &CellAttributes::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Line {
|
pub struct Line {
|
||||||
pub cells: Vec<Cell>,
|
pub cells: Vec<Cell>,
|
||||||
@ -223,6 +243,12 @@ impl Line {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a str> for Line {
|
||||||
|
fn from(s: &str) -> Line {
|
||||||
|
Line::from_text(s, &CellAttributes::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Holds the model of a screen. This can either be the primary screen
|
/// Holds the model of a screen. This can either be the primary screen
|
||||||
/// which includes lines of scrollback text, or the alternate screen
|
/// which includes lines of scrollback text, or the alternate screen
|
||||||
/// which holds no scrollback. The intent is to have one instance of
|
/// which holds no scrollback. The intent is to have one instance of
|
||||||
@ -307,6 +333,12 @@ impl Screen {
|
|||||||
self.lines[line_idx].dirty = false;
|
self.lines[line_idx].dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a slice over the visible lines in the screen (no scrollback)
|
||||||
|
fn visible_lines(&self) -> &[Line] {
|
||||||
|
let line_idx = self.lines.len() - self.physical_rows;
|
||||||
|
&self.lines[line_idx..line_idx + self.physical_rows]
|
||||||
|
}
|
||||||
|
|
||||||
/// Set a cell. the x and y coordinates are relative to the visible screeen
|
/// Set a cell. the x and y coordinates are relative to the visible screeen
|
||||||
/// origin. 0,0 is the top left.
|
/// origin. 0,0 is the top left.
|
||||||
pub fn set_cell(&mut self, x: usize, y: usize, c: char, attr: &CellAttributes) {
|
pub fn set_cell(&mut self, x: usize, y: usize, c: char, attr: &CellAttributes) {
|
||||||
@ -324,13 +356,13 @@ impl Screen {
|
|||||||
let width = cells.len();
|
let width = cells.len();
|
||||||
// if the line isn't wide enough, pad it out with the default attributes
|
// if the line isn't wide enough, pad it out with the default attributes
|
||||||
if x >= width {
|
if x >= width {
|
||||||
cells.resize(x + 1, Cell::from_char(' ', &CellAttributes::default()));
|
cells.resize(x + 1, Cell::default());
|
||||||
}
|
}
|
||||||
cells[x] = Cell::from_char(c, attr);
|
cells[x] = Cell::from_char(c, attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_line(&mut self, y: usize, cols: std::ops::Range<usize>) {
|
pub fn clear_line(&mut self, y: usize, cols: std::ops::Range<usize>) {
|
||||||
let blank = Cell::from_char(' ', &CellAttributes::default());
|
let blank = Cell::default();
|
||||||
let line_idx = (self.lines.len() - self.physical_rows) + y;
|
let line_idx = (self.lines.len() - self.physical_rows) + y;
|
||||||
let line = self.line_mut(line_idx);
|
let line = self.line_mut(line_idx);
|
||||||
let max_col = line.cells.len();
|
let max_col = line.cells.len();
|
||||||
@ -881,13 +913,23 @@ impl vte::Perform for TerminalState {
|
|||||||
}
|
}
|
||||||
CSIAction::DeleteLines(n) => {
|
CSIAction::DeleteLines(n) => {
|
||||||
let top = self.cursor_y;
|
let top = self.cursor_y;
|
||||||
println!("execute delete {} lines with scroll up {} {}", n, top, top+n);
|
println!(
|
||||||
self.screen_mut().scroll_up(top, top+n, n);
|
"execute delete {} lines with scroll up {} {}",
|
||||||
|
n,
|
||||||
|
top,
|
||||||
|
top + n
|
||||||
|
);
|
||||||
|
self.screen_mut().scroll_up(top, top + n, n);
|
||||||
}
|
}
|
||||||
CSIAction::InsertLines(n) => {
|
CSIAction::InsertLines(n) => {
|
||||||
let top = self.cursor_y;
|
let top = self.cursor_y;
|
||||||
println!("execute insert {} lines with scroll down {} {}", n, top, top+n);
|
println!(
|
||||||
self.screen_mut().scroll_down(top, top+n, n);
|
"execute insert {} lines with scroll down {} {}",
|
||||||
|
n,
|
||||||
|
top,
|
||||||
|
top + n
|
||||||
|
);
|
||||||
|
self.screen_mut().scroll_down(top, top + n, n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
121
src/term/test/mod.rs
Normal file
121
src/term/test/mod.rs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
//! Various tests of the terminal model and escape sequence
|
||||||
|
//! processing routines.
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn set_mode(term: &mut Terminal, mode: &str, enable: bool) {
|
||||||
|
term.advance_bytes(CSI);
|
||||||
|
term.advance_bytes(mode);
|
||||||
|
term.advance_bytes(if enable { b"h" } else { b"l" });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cup(term: &mut Terminal, row: isize, col: isize) {
|
||||||
|
term.advance_bytes(CSI);
|
||||||
|
term.advance_bytes(format!("{};{}H", row, col));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn erase_in_display(term: &mut Terminal, erase: DisplayErase) {
|
||||||
|
term.advance_bytes(CSI);
|
||||||
|
let num = match erase {
|
||||||
|
DisplayErase::Below => 0,
|
||||||
|
DisplayErase::Above => 1,
|
||||||
|
DisplayErase::All => 2,
|
||||||
|
DisplayErase::SavedLines => 3,
|
||||||
|
};
|
||||||
|
term.advance_bytes(format!("{}J", num));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn erase_in_line(term: &mut Terminal, erase: LineErase) {
|
||||||
|
term.advance_bytes(CSI);
|
||||||
|
let num = match erase {
|
||||||
|
LineErase::ToRight => 0,
|
||||||
|
LineErase::ToLeft => 1,
|
||||||
|
LineErase::All => 2,
|
||||||
|
};
|
||||||
|
term.advance_bytes(format!("{}K", num));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts that the visible lines of the terminal have the
|
||||||
|
/// same cell contents. The cells must exactly match.
|
||||||
|
fn assert_visible_lines(term: &Terminal, expect_lines: &[Line]) {
|
||||||
|
let screen = term.screen();
|
||||||
|
let lines = screen.visible_lines();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
lines.len() == expect_lines.len(),
|
||||||
|
"expectation has wrong number of lines"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut expect_iter = expect_lines.iter();
|
||||||
|
|
||||||
|
for (idx, line) in lines.iter().enumerate() {
|
||||||
|
let expect = expect_iter.next().unwrap();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
expect.cells == line.cells,
|
||||||
|
"line {} was {:?} but expected {:?}",
|
||||||
|
idx,
|
||||||
|
line.cells,
|
||||||
|
expect.cells
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts that the visible lines of the terminal have the
|
||||||
|
/// same character contents as the expected lines.
|
||||||
|
/// The other cell attributes are not compared; this is
|
||||||
|
/// a convenience for writing visually understandable tests.
|
||||||
|
fn assert_visible_contents(term: &Terminal, expect_lines: &[&str]) {
|
||||||
|
let screen = term.screen();
|
||||||
|
let lines = screen.visible_lines();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
lines.len() == expect_lines.len(),
|
||||||
|
"expectation has wrong number of lines"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut expect_iter = expect_lines.iter();
|
||||||
|
|
||||||
|
for (idx, line) in lines.iter().enumerate() {
|
||||||
|
let expect = expect_iter.next().unwrap();
|
||||||
|
let line_str = line.as_str();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
&line_str == expect,
|
||||||
|
"line {} was {:?} but expected {:?}",
|
||||||
|
idx,
|
||||||
|
line_str,
|
||||||
|
expect
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic_output() {
|
||||||
|
let mut term = Terminal::new(5, 10, 0);
|
||||||
|
|
||||||
|
cup(&mut term, 2, 2);
|
||||||
|
term.advance_bytes("hello, world!");
|
||||||
|
assert_visible_contents(
|
||||||
|
&term,
|
||||||
|
&[
|
||||||
|
" ",
|
||||||
|
" hello, wo",
|
||||||
|
"rld! ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
erase_in_display(&mut term, DisplayErase::Above);
|
||||||
|
assert_visible_contents(
|
||||||
|
&term,
|
||||||
|
&[
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
"rld! ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user