aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2019-11-05 14:01:42 -0500
committerJesse Luehrs <doy@tozt.net>2019-11-05 14:09:26 -0500
commit2471941ad0ee28a0c27df3f007faa16ff7028fa8 (patch)
tree6415ff09131f50ce020522ddcc98f4268343d91e
parent2e7f1686d719497d9b2d2d2c8ffba20e6c8214bd (diff)
downloadvt100-rust-2471941ad0ee28a0c27df3f007faa16ff7028fa8.tar.gz
vt100-rust-2471941ad0ee28a0c27df3f007faa16ff7028fa8.zip
add functionality for diffing two terminal screens
-rw-r--r--src/cell.rs2
-rw-r--r--src/grid.rs19
-rw-r--r--src/row.rs40
-rw-r--r--src/screen.rs4
-rw-r--r--tests/window_contents.rs89
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();
diff --git a/src/row.rs b/src/row.rs
index ade6408..9322c36 100644
--- a/src/row.rs
+++ b/src/row.rs
@@ -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());