diff options
author | Jesse Luehrs <doy@tozt.net> | 2019-11-05 14:01:42 -0500 |
---|---|---|
committer | Jesse Luehrs <doy@tozt.net> | 2019-11-05 14:09:26 -0500 |
commit | 2471941ad0ee28a0c27df3f007faa16ff7028fa8 (patch) | |
tree | 6415ff09131f50ce020522ddcc98f4268343d91e | |
parent | 2e7f1686d719497d9b2d2d2c8ffba20e6c8214bd (diff) | |
download | vt100-rust-2471941ad0ee28a0c27df3f007faa16ff7028fa8.tar.gz vt100-rust-2471941ad0ee28a0c27df3f007faa16ff7028fa8.zip |
add functionality for diffing two terminal screens
-rw-r--r-- | src/cell.rs | 2 | ||||
-rw-r--r-- | src/grid.rs | 19 | ||||
-rw-r--r-- | src/row.rs | 40 | ||||
-rw-r--r-- | src/screen.rs | 4 | ||||
-rw-r--r-- | tests/window_contents.rs | 89 |
5 files changed, 134 insertions, 20 deletions
diff --git a/src/cell.rs b/src/cell.rs index da6c86f..285d37a 100644 --- a/src/cell.rs +++ b/src/cell.rs @@ -1,7 +1,7 @@ use unicode_normalization::UnicodeNormalization as _; /// Represents a single terminal cell. -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct Cell { contents: String, attrs: crate::attrs::Attrs, diff --git a/src/grid.rs b/src/grid.rs index 14a3eae..2ded0e2 100644 --- a/src/grid.rs +++ b/src/grid.rs @@ -1,3 +1,5 @@ +use std::convert::TryInto as _; + #[derive(Clone, Debug)] pub struct Grid { size: Size, @@ -153,6 +155,23 @@ impl Grid { contents } + pub fn contents_diff(&self, prev: &Self) -> Vec<u8> { + let mut contents = vec![]; + let mut prev_attrs = crate::attrs::Attrs::default(); + for (idx, (row, prev_row)) in self.rows().zip(prev.rows()).enumerate() + { + let (mut new_contents, new_attrs) = row.contents_diff( + idx.try_into().unwrap(), + prev_row, + prev_attrs, + ); + contents.append(&mut new_contents); + prev_attrs = new_attrs; + } + + contents + } + pub fn erase_all(&mut self) { for row in self.rows_mut() { row.clear(); @@ -126,6 +126,46 @@ impl Row { (contents, prev_attrs) } + pub fn contents_diff( + &self, + row_idx: u16, + prev: &Self, + attrs: crate::attrs::Attrs, + ) -> (Vec<u8>, crate::attrs::Attrs) { + let mut needs_move = true; + let mut contents = vec![]; + let mut prev_attrs = attrs; + for (idx, (cell, prev_cell)) in + self.cells().zip(prev.cells()).enumerate() + { + if cell == prev_cell { + needs_move = true; + } else { + if needs_move { + contents.extend( + format!("\x1b[{};{}H", row_idx + 1, idx + 1) + .as_bytes(), + ); + needs_move = false; + } + + let attrs = cell.attrs(); + if &prev_attrs != attrs { + contents.append(&mut attrs.escape_code_diff(&prev_attrs)); + prev_attrs = *attrs; + } + + contents.extend(if cell.has_contents() { + cell.contents().as_bytes() + } else { + b"\x1b[X" + }); + } + } + + (contents, prev_attrs) + } + fn content_width(&self, start: u16) -> u16 { for (col, cell) in self.cells.iter().skip(start as usize).enumerate().rev() diff --git a/src/screen.rs b/src/screen.rs index dd30b19..7d6fca6 100644 --- a/src/screen.rs +++ b/src/screen.rs @@ -183,6 +183,10 @@ impl Screen { }) } + pub fn contents_diff(&self, prev: &Self) -> Vec<u8> { + self.grid().contents_diff(prev.grid()) + } + /// Returns the `Cell` object at the given location in the terminal, if it /// exists. pub fn cell(&self, row: u16, col: u16) -> Option<&crate::cell::Cell> { diff --git a/tests/window_contents.rs b/tests/window_contents.rs index ccf5342..723e0d8 100644 --- a/tests/window_contents.rs +++ b/tests/window_contents.rs @@ -1,11 +1,11 @@ #[test] fn formatted() { let mut parser = vt100::Parser::new(24, 80); - compare_formatted(&parser); + compare_formatted(parser.screen()); assert_eq!(parser.screen().contents_formatted(), b""); parser.process(b"foobar"); - compare_formatted(&parser); + compare_formatted(parser.screen()); assert!(!parser.screen().cell(0, 2).unwrap().bold()); assert!(!parser.screen().cell(0, 3).unwrap().bold()); assert!(!parser.screen().cell(0, 4).unwrap().bold()); @@ -13,7 +13,7 @@ fn formatted() { assert_eq!(parser.screen().contents_formatted(), b"foobar"); parser.process(b"\x1b[1;4H\x1b[1;7m\x1b[33mb"); - compare_formatted(&parser); + compare_formatted(parser.screen()); assert!(!parser.screen().cell(0, 2).unwrap().bold()); assert!(parser.screen().cell(0, 3).unwrap().bold()); assert!(!parser.screen().cell(0, 4).unwrap().bold()); @@ -24,7 +24,7 @@ fn formatted() { ); parser.process(b"\x1b[1;5H\x1b[22;42ma"); - compare_formatted(&parser); + compare_formatted(parser.screen()); assert!(!parser.screen().cell(0, 2).unwrap().bold()); assert!(parser.screen().cell(0, 3).unwrap().bold()); assert!(!parser.screen().cell(0, 4).unwrap().bold()); @@ -35,14 +35,14 @@ fn formatted() { ); parser.process(b"\x1b[1;6H\x1b[35mr\r\nquux"); - compare_formatted(&parser); + compare_formatted(parser.screen()); assert_eq!( parser.screen().contents_formatted(), &b"foo\x1b[33;1;7mb\x1b[42;22ma\x1b[35mr\r\nquux"[..] ); parser.process(b"\x1b[2;1H\x1b[45mquux"); - compare_formatted(&parser); + compare_formatted(parser.screen()); assert_eq!( parser.screen().contents_formatted(), &b"foo\x1b[33;1;7mb\x1b[42;22ma\x1b[35mr\r\n\x1b[45mquux"[..] @@ -50,7 +50,7 @@ fn formatted() { parser .process(b"\x1b[2;2H\x1b[38;2;123;213;231mu\x1b[38;5;254mu\x1b[39mx"); - compare_formatted(&parser); + compare_formatted(parser.screen()); assert_eq!(parser.screen().contents_formatted(), &b"foo\x1b[33;1;7mb\x1b[42;22ma\x1b[35mr\r\n\x1b[45mq\x1b[38;2;123;213;231mu\x1b[38;5;254mu\x1b[39mx"[..]); } @@ -58,7 +58,7 @@ fn formatted() { fn empty_cells() { let mut parser = vt100::Parser::new(24, 80); parser.process(b"\x1b[5C\x1b[32m bar\x1b[H\x1b[31mfoo"); - compare_formatted(&parser); + compare_formatted(parser.screen()); assert_eq!(parser.screen().contents(), "foo bar"); assert_eq!( parser.screen().contents_formatted(), @@ -318,22 +318,73 @@ fn rows() { ); } -fn compare_formatted(parser: &vt100::Parser) { - let (rows, cols) = parser.screen().size(); - let contents = parser.screen().contents_formatted(); - let mut parser2 = vt100::Parser::new(rows, cols); - parser2.process(&contents); - compare_cells(parser, &parser2); +#[test] +fn diff() { + let mut parser = vt100::Parser::new(24, 80); + let screen1 = parser.screen().clone(); + parser.process(b"\x1b[5C\x1b[32m bar"); + let screen2 = parser.screen().clone(); + assert_eq!(screen2.contents_diff(&screen1), b"\x1b[1;6H\x1b[32m bar"); + compare_diff(&screen1, &screen2, b""); + + parser.process(b"\x1b[H\x1b[31mfoo"); + let screen3 = parser.screen().clone(); + assert_eq!(screen3.contents_diff(&screen2), b"\x1b[1;1H\x1b[31mfoo"); + compare_diff(&screen2, &screen3, b"\x1b[5C\x1b[32m bar"); + + parser.process(b"\x1b[1;7H\x1b[32mbaz"); + let screen4 = parser.screen().clone(); + assert_eq!(screen4.contents_diff(&screen3), b"\x1b[1;9H\x1b[32mz"); + compare_diff(&screen3, &screen4, b"\x1b[5C\x1b[32m bar\x1b[H\x1b[31mfoo"); + + parser.process(b"\x1b[1;8H\x1b[X"); + let screen5 = parser.screen().clone(); + assert_eq!(screen5.contents_diff(&screen4), b"\x1b[1;8H\x1b[X"); + compare_diff( + &screen4, + &screen5, + b"\x1b[5C\x1b[32m bar\x1b[H\x1b[31mfoo\x1b[1;7H\x1b[32mbaz", + ); +} + +fn compare_formatted(screen: &vt100::Screen) { + let (rows, cols) = screen.size(); + let mut parser = vt100::Parser::new(rows, cols); + let contents = screen.contents_formatted(); + parser.process(&contents); + compare_cells(screen, parser.screen()); +} + +fn compare_diff( + prev_screen: &vt100::Screen, + screen: &vt100::Screen, + prev_parsed: &[u8], +) { + let (rows, cols) = screen.size(); + let mut parser = vt100::Parser::new(rows, cols); + parser.process(prev_parsed); + assert_eq!( + parser.screen().contents_formatted(), + prev_screen.contents_formatted() + ); + compare_cells(parser.screen(), &prev_screen); + + parser.process(&screen.contents_diff(prev_screen)); + assert_eq!( + parser.screen().contents_formatted(), + screen.contents_formatted() + ); + compare_cells(parser.screen(), &screen); } -fn compare_cells(parser1: &vt100::Parser, parser2: &vt100::Parser) { - assert_eq!(parser1.screen().size(), parser2.screen().size()); - let (rows, cols) = parser1.screen().size(); +fn compare_cells(screen1: &vt100::Screen, screen2: &vt100::Screen) { + assert_eq!(screen1.size(), screen2.size()); + let (rows, cols) = screen1.size(); for row in 0..rows { for col in 0..cols { - let cell1 = parser1.screen().cell(row, col).unwrap(); - let cell2 = parser2.screen().cell(row, col).unwrap(); + let cell1 = screen1.cell(row, col).unwrap(); + let cell2 = screen2.cell(row, col).unwrap(); assert_eq!(cell1.contents(), cell2.contents()); assert_eq!(cell1.fgcolor(), cell2.fgcolor()); |