1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-20 03:41:36 +03:00

Implement screen/region diffing and copying

This commit is contained in:
Wez Furlong 2018-07-16 08:19:29 -07:00
parent 30a549bdab
commit 2830da269e
2 changed files with 222 additions and 6 deletions

View File

@ -180,3 +180,16 @@ pub enum AttributeChange {
Background(ColorAttribute), Background(ColorAttribute),
Hyperlink(Option<Rc<Hyperlink>>), Hyperlink(Option<Rc<Hyperlink>>),
} }
#[cfg(test)]
mod test {
use super::*;
#[test]
fn nerf_special() {
for c in " \n\r\t".chars() {
let cell = Cell::new(c, CellAttributes::default());
assert_eq!(cell.char(), ' ');
}
}
}

View File

@ -387,17 +387,124 @@ impl Screen {
result result
} }
/// Computes the change stream required to make the region within `self`
/// at coordinates `x`, `y` and size `width`, `height` look like the
/// same sized region within `other` at coordinates `other_x`, `other_y`.
/// # Panics
/// Will panic if the regions of interest are not within the bounds of
/// their respective `Screen`.
pub fn diff_region(
&self,
x: usize,
y: usize,
width: usize,
height: usize,
other: &Screen,
other_x: usize,
other_y: usize,
) -> Vec<Change> {
assert!(x + width <= self.width);
assert!(y + height <= self.height);
assert!(other_x + width <= other.width);
assert!(other_y + height <= other.height);
let mut result = Vec::new();
// Keep track of the cursor position that the change stream
// selects for updates so that we can avoid emitting redundant
// position changes.
let mut cursor = None;
// Similarly, we keep track of the cell attributes that we have
// activated for change stream to avoid over-emitting.
// Tracking the cursor and attributes in this way helps to coalesce
// lines of text into simpler strings.
let mut attr: Option<CellAttributes> = None;
for ((row_num, line), other_line) in self.lines
.iter()
.enumerate()
.skip(y)
.take_while(|(row_num, _)| *row_num < y + height)
.zip(other.lines.iter().skip(other_y))
{
for ((col_num, cell), other_cell) in line.cells
.iter()
.enumerate()
.skip(x)
.take_while(|(col_num, _)| *col_num < x + width)
.zip(other_line.cells.iter().skip(other_x))
{
if cell != other_cell {
cursor = match cursor.take() {
Some((cursor_row, cursor_col))
if cursor_row == row_num && cursor_col == col_num - 1 =>
{
// It is on the column prior, so we don't need
// to explicitly move it. Record the effective
// position for next time.
Some((row_num, col_num))
}
_ => {
// Need to explicitly move the cursor
result.push(Change::CursorPosition {
y: Position::Absolute(row_num),
x: Position::Absolute(col_num),
});
// and remember the position for next time
Some((row_num, col_num))
}
};
// we could get fancy and try to minimize the update traffic
// by computing a series of AttributeChange values here.
// For now, let's just record the new value
attr = match attr.take() {
Some(ref attr) if attr == other_cell.attrs() => {
// Active attributes match, so we don't need
// to emit a change for them
Some(attr.clone())
}
_ => {
// Attributes are different
result.push(Change::AllAttributes(other_cell.attrs().clone()));
Some(other_cell.attrs().clone())
}
};
if cell.char() != other_cell.char() {
// A little bit of bloat in the code to avoid runs of single
// character Text entries; just append to the string.
let result_len = result.len();
if result_len > 0 && result[result_len - 1].is_text() {
if let Some(Change::Text(ref mut prefix)) =
result.get_mut(result_len - 1)
{
prefix.push(other_cell.char());
}
} else {
result.push(Change::Text(other_cell.char().to_string()));
}
}
}
}
}
result
}
/// Computes the change stream required to make `self` have the same /// Computes the change stream required to make `self` have the same
/// screen contents as `other`. /// screen contents as `other`.
pub fn diff_screens(&self, _other: &Screen) -> Vec<Change> { pub fn diff_screens(&self, other: &Screen) -> Vec<Change> {
unimplemented!() self.diff_region(0, 0, self.width, self.height, other, 0, 0)
} }
/// Draw the contents of `other` into self at the specified coordinates. /// Draw the contents of `other` into self at the specified coordinates.
/// The required updates are recorded as Change entries as well as stored /// The required updates are recorded as Change entries as well as stored
/// in the screen line/cell data. /// in the screen line/cell data.
pub fn draw_from_screen(&mut self, _other: &Screen, _x: usize, _y: usize) { pub fn draw_from_screen(&mut self, other: &Screen, x: usize, y: usize) -> SequenceNo {
unimplemented!() let mut seq = 0;
for change in self.diff_region(x, y, other.width, other.height, other, 0, 0) {
seq = self.add_change(change);
}
seq
} }
} }
@ -422,6 +529,7 @@ fn compute_position_change(current: usize, pos: &Position, limit: usize) -> usiz
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use cell::Intensity;
// The \x20's look a little awkward, but we can't use a plain // The \x20's look a little awkward, but we can't use a plain
// space in the first chararcter of a multi-line continuation; // space in the first chararcter of a multi-line continuation;
@ -669,8 +777,6 @@ mod test {
seq seq
}; };
use cell::Intensity;
// prep some deltas for the loop to test below // prep some deltas for the loop to test below
{ {
s.add_change(Change::Attribute(AttributeChange::Intensity( s.add_change(Change::Attribute(AttributeChange::Intensity(
@ -706,4 +812,101 @@ mod test {
s.flush_changes_older_than(seq_pos); s.flush_changes_older_than(seq_pos);
} }
} }
#[test]
fn diff_screens() {
let mut s = Screen::new(4, 3);
s.add_change("w00t");
s.add_change("foo");
s.add_change("baar");
s.add_change("baz");
assert_eq!(
s.screen_chars_to_string(),
"foob\n\
aarb\n\
az \n"
);
let s2 = Screen::new(2, 2);
{
// We want to sample the top left corner
let changes = s2.diff_region(0, 0, 2, 2, &s, 0, 0);
assert_eq!(
vec![
Change::CursorPosition {
x: Position::Absolute(0),
y: Position::Absolute(0),
},
Change::AllAttributes(CellAttributes::default()),
Change::Text("fo".into()),
Change::CursorPosition {
x: Position::Absolute(0),
y: Position::Absolute(1),
},
Change::Text("aa".into()),
],
changes
);
}
// Throw in some attribute changes too
s.add_change(Change::CursorPosition {
x: Position::Absolute(1),
y: Position::Absolute(1),
});
s.add_change(Change::Attribute(AttributeChange::Intensity(
Intensity::Bold,
)));
s.add_change("XO");
{
let changes = s2.diff_region(0, 0, 2, 2, &s, 1, 1);
assert_eq!(
vec![
Change::CursorPosition {
x: Position::Absolute(0),
y: Position::Absolute(0),
},
Change::AllAttributes(
CellAttributes::default()
.set_intensity(Intensity::Bold)
.clone(),
),
Change::Text("XO".into()),
Change::CursorPosition {
x: Position::Absolute(0),
y: Position::Absolute(1),
},
Change::AllAttributes(CellAttributes::default()),
Change::Text("z".into()),
/* There's no change for the final character
* position because it is a space in both regions. */
],
changes
);
}
}
#[test]
fn draw_screens() {
let mut s = Screen::new(4, 4);
let mut s1 = Screen::new(2, 2);
s1.add_change("1234");
let mut s2 = Screen::new(2, 2);
s2.add_change("XYZA");
s.draw_from_screen(&s1, 0, 0);
s.draw_from_screen(&s2, 2, 2);
assert_eq!(
s.screen_chars_to_string(),
"12 \n\
34 \n\
\x20\x20XY\n\
\x20\x20ZA\n"
);
}
} }