From b04c0e6e97765aeb888479c5e0bc27d54de60659 Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Sun, 10 Nov 2019 04:51:22 -0500 Subject: optimize cursor movement a bit --- src/grid.rs | 57 +++++++--------- src/lib.rs | 1 + src/row.rs | 171 +++++++++++++++++++++++++++++++++-------------- src/screen.rs | 14 +++- tests/csi.rs | 36 +++++----- tests/escape.rs | 6 +- tests/scroll.rs | 18 +++++ tests/text.rs | 10 +-- tests/window_contents.rs | 39 ++++------- 9 files changed, 211 insertions(+), 141 deletions(-) diff --git a/src/grid.rs b/src/grid.rs index ee608ca..1cc4daf 100644 --- a/src/grid.rs +++ b/src/grid.rs @@ -189,30 +189,25 @@ impl Grid { .unwrap(); let mut prev_attrs = crate::attrs::Attrs::default(); - let mut final_col = 0; - for row in self.visible_rows() { - let (new_attrs, new_col) = row.write_contents_formatted( + let mut prev_pos = Pos::default(); + let mut wrapping = false; + for (i, row) in self.visible_rows().enumerate() { + let i = i.try_into().unwrap(); + let (new_pos, new_attrs) = row.write_contents_formatted( contents, 0, self.size.cols, + i, + wrapping, + prev_pos, prev_attrs, ); - if let Some(col) = new_col { - final_col = col; - } - if !row.wrapped() { - write!(contents, "{}", crate::term::CRLF::new()).unwrap(); - } + prev_pos = new_pos; prev_attrs = new_attrs; + wrapping = row.wrapped(); } - let mut final_row = self.size.rows; - while contents.ends_with(b"\r\n") { - contents.truncate(contents.len() - 2); - final_row -= 1; - } - - if final_row != self.pos.row || final_col != self.pos.col { + if prev_pos != self.pos { write!(contents, "{}", crate::term::MoveTo::new(self.pos)) .unwrap(); } @@ -220,36 +215,30 @@ impl Grid { pub fn write_contents_diff(&self, contents: &mut Vec, prev: &Self) { write!(contents, "{}", crate::term::Attrs::default()).unwrap(); + + let mut prev_pos = prev.pos; let mut prev_attrs = crate::attrs::Attrs::default(); - let mut final_row = prev.pos.row; - let mut final_col = prev.pos.col; - for (idx, (row, prev_row)) in + let mut wrapping = false; + for (i, (row, prev_row)) in self.visible_rows().zip(prev.visible_rows()).enumerate() { - let idx = idx.try_into().unwrap(); - let (new_attrs, new_col) = row.write_contents_diff( + let i = i.try_into().unwrap(); + let (new_pos, new_attrs) = row.write_contents_diff( contents, prev_row, - |contents| { - write!( - contents, - "{}", - crate::term::MoveTo::new(Pos { row: idx, col: 0 }) - ) - .unwrap(); - }, 0, self.size.cols, + i, + wrapping, + prev_pos, prev_attrs, ); - if let Some(col) = new_col { - final_row = idx; - final_col = col; - } + prev_pos = new_pos; prev_attrs = new_attrs; + wrapping = row.wrapped(); } - if self.pos.row != final_row || self.pos.col != final_col { + if prev_pos != self.pos { write!(contents, "{}", crate::term::MoveTo::new(self.pos)) .unwrap(); } diff --git a/src/lib.rs b/src/lib.rs index d14b78f..7f391c6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,7 @@ #![warn(clippy::nursery)] #![allow(clippy::missing_const_for_fn)] #![allow(clippy::single_match)] +#![allow(clippy::too_many_arguments)] mod attrs; mod cell; diff --git a/src/row.rs b/src/row.rs index 8008212..f7d4485 100644 --- a/src/row.rs +++ b/src/row.rs @@ -15,6 +15,10 @@ impl Row { } } + fn cols(&self) -> u16 { + self.cells.len().try_into().unwrap() + } + pub fn clear(&mut self, bgcolor: crate::attrs::Color) { for cell in &mut self.cells { cell.clear(bgcolor); @@ -99,12 +103,16 @@ impl Row { contents: &mut Vec, start: u16, width: u16, + row: u16, + wrapping: bool, + pos: crate::grid::Pos, attrs: crate::attrs::Attrs, - ) -> (crate::attrs::Attrs, Option) { + ) -> (crate::grid::Pos, crate::attrs::Attrs) { let mut prev_was_wide = false; + let mut prev_pos = pos; let mut prev_attrs = attrs; - let mut cols = None; + let mut new_pos = crate::grid::Pos { row, col: start }; for cell in self .cells() .skip(start as usize) @@ -115,52 +123,92 @@ impl Row { continue; } - let attrs = cell.attrs(); - if &prev_attrs != attrs { - attrs.write_escape_code_diff(contents, &prev_attrs); - prev_attrs = *attrs; - } + prev_was_wide = cell.is_wide(); - if cell.has_contents() { - // using write! here is significantly slower, for some reason - // write!(contents, "{}", cell.contents()).unwrap(); - contents.extend(cell.contents().as_bytes()); - } else if cell.bgcolor() == crate::attrs::Color::Default { - write!(contents, "{}", crate::term::MoveRight::default()) - .unwrap(); + let has_contents = cell.has_contents(); + if !has_contents && cell.bgcolor() == crate::attrs::Color::Default + { + new_pos.col += 1; } else { - write!( - contents, - "{}{}", - crate::term::EraseChar::default(), - crate::term::MoveRight::default() - ) - .unwrap(); - } + if new_pos != prev_pos { + if new_pos.row == prev_pos.row + 1 { + if !wrapping + || prev_pos.col != self.cols() + || new_pos.col != 0 + { + write!( + contents, + "{}{}", + crate::term::CRLF::default(), + crate::term::MoveRight::new(new_pos.col) + ) + .unwrap(); + } + } else if prev_pos.row == new_pos.row { + write!( + contents, + "{}", + crate::term::MoveRight::new( + new_pos.col - prev_pos.col + ) + ) + .unwrap(); + } else { + write!( + contents, + "{}", + crate::term::MoveTo::new(new_pos) + ) + .unwrap(); + } + prev_pos = new_pos; + } - prev_was_wide = cell.is_wide(); - cols = - Some(cols.unwrap_or(0) + if prev_was_wide { 2 } else { 1 }); + let attrs = cell.attrs(); + if &prev_attrs != attrs { + attrs.write_escape_code_diff(contents, &prev_attrs); + prev_attrs = *attrs; + } + + if has_contents { + // using write! here is significantly slower, for some + // reason + // write!(contents, "{}", cell.contents()).unwrap(); + contents.extend(cell.contents().as_bytes()); + let width = if prev_was_wide { 2 } else { 1 }; + prev_pos.col += width; + new_pos.col += width; + } else { + write!( + contents, + "{}", + crate::term::EraseChar::default(), + ) + .unwrap(); + new_pos.col += 1; + } + } } - (prev_attrs, cols) + (prev_pos, prev_attrs) } - pub fn write_contents_diff Fn(&'a mut Vec)>( + pub fn write_contents_diff( &self, contents: &mut Vec, prev: &Self, - initial_pos: F, start: u16, width: u16, + row: u16, + wrapping: bool, + pos: crate::grid::Pos, attrs: crate::attrs::Attrs, - ) -> (crate::attrs::Attrs, Option) { + ) -> (crate::grid::Pos, crate::attrs::Attrs) { let mut prev_was_wide = false; + let mut prev_pos = pos; let mut prev_attrs = attrs; - let mut skip = 0; - let mut cols = None; - let mut initial_pos = Some(initial_pos); + let mut new_pos = crate::grid::Pos { row, col: start }; for (cell, prev_cell) in self .cells() .zip(prev.cells()) @@ -172,18 +220,45 @@ impl Row { continue; } + prev_was_wide = cell.is_wide(); + if cell == prev_cell { - prev_was_wide = cell.is_wide(); - skip += if prev_was_wide { 2 } else { 1 }; + new_pos.col += if prev_was_wide { 2 } else { 1 }; } else { - if let Some(f) = initial_pos.take() { - f(contents) - } - if skip > 0 { - write!(contents, "{}", crate::term::MoveRight::new(skip)) + if new_pos != prev_pos { + if new_pos.row == prev_pos.row + 1 { + if !wrapping + || prev_pos.col != self.cols() + || new_pos.col != 0 + { + write!( + contents, + "{}{}", + crate::term::CRLF::default(), + crate::term::MoveRight::new(new_pos.col) + ) + .unwrap(); + } + } else if prev_pos.row == new_pos.row + && prev_pos.col < new_pos.col + { + write!( + contents, + "{}", + crate::term::MoveRight::new( + new_pos.col - prev_pos.col + ) + ) .unwrap(); - cols = Some(cols.unwrap_or(0) + skip); - skip = 0; + } else { + write!( + contents, + "{}", + crate::term::MoveTo::new(new_pos) + ) + .unwrap(); + } + prev_pos = new_pos; } let attrs = cell.attrs(); @@ -197,24 +272,22 @@ impl Row { // reason // write!(contents, "{}", cell.contents()).unwrap(); contents.extend(cell.contents().as_bytes()); + let width = if prev_was_wide { 2 } else { 1 }; + prev_pos.col += width; + new_pos.col += width; } else { write!( contents, - "{}{}", + "{}", crate::term::EraseChar::default(), - crate::term::MoveRight::default() ) .unwrap(); + new_pos.col += 1; } - - prev_was_wide = cell.is_wide(); - cols = Some( - cols.unwrap_or(0) + if prev_was_wide { 2 } else { 1 }, - ); } } - (prev_attrs, cols) + (prev_pos, prev_attrs) } fn content_width(&self, start: u16, formatting: bool) -> u16 { diff --git a/src/screen.rs b/src/screen.rs index 5150e3a..b774c10 100644 --- a/src/screen.rs +++ b/src/screen.rs @@ -207,12 +207,16 @@ impl Screen { start: u16, width: u16, ) -> impl Iterator> + '_ { - self.grid().visible_rows().map(move |row| { + self.grid().visible_rows().enumerate().map(move |(i, row)| { + let i = i.try_into().unwrap(); let mut contents = vec![]; row.write_contents_formatted( &mut contents, start, width, + i, + false, + crate::grid::Pos { row: i, col: start }, crate::attrs::Attrs::default(), ); contents @@ -263,14 +267,18 @@ impl Screen { self.grid() .visible_rows() .zip(prev.grid().visible_rows()) - .map(move |(row, prev_row)| { + .enumerate() + .map(move |(i, (row, prev_row))| { + let i = i.try_into().unwrap(); let mut contents = vec![]; row.write_contents_diff( &mut contents, prev_row, - |_| (), start, width, + i, + false, + crate::grid::Pos { row: i, col: start }, crate::attrs::Attrs::default(), ); contents diff --git a/tests/csi.rs b/tests/csi.rs index 1c1ed46..4f06cd3 100644 --- a/tests/csi.rs +++ b/tests/csi.rs @@ -207,12 +207,10 @@ fn ed() { assert_eq!( parser.screen().contents_formatted(), format!( - "\x1b[?25h\x1b[m\x1b[H\x1b[J{}{}\x1b[41m{}\r\n{}{}\x1b[5;5H", - "\r\n".repeat(4), - "\x1b[C".repeat(4), - "\x1b[X\x1b[C".repeat(76), - format!("{}\r\n", "\x1b[X\x1b[C".repeat(80)).repeat(18), - "\x1b[X\x1b[C".repeat(80), + "\x1b[?25h\x1b[m\x1b[H\x1b[J\x1b[5;5H\x1b[41m{}\x1b[X\r\n{}{}\x1b[X\x1b[5;5H", + "\x1b[X\x1b[C".repeat(75), + format!("{}\x1b[X\r\n", "\x1b[X\x1b[C".repeat(79)).repeat(18), + "\x1b[X\x1b[C".repeat(79), ) .as_bytes() ); @@ -259,9 +257,9 @@ fn ed() { assert_eq!( parser.screen().contents_formatted(), format!( - "\x1b[?25h\x1b[m\x1b[H\x1b[J\x1b[41m{}{}\x1b[5;5H", - format!("{}\r\n", "\x1b[X\x1b[C".repeat(80)).repeat(4), - "\x1b[X\x1b[C".repeat(5), + "\x1b[?25h\x1b[m\x1b[H\x1b[J\x1b[41m{}{}\x1b[X", + format!("{}\x1b[X\r\n", "\x1b[X\x1b[C".repeat(79)).repeat(4), + "\x1b[X\x1b[C".repeat(4), ) .as_bytes() ); @@ -309,8 +307,8 @@ fn ed() { parser.screen().contents_formatted(), format!( "\x1b[?25h\x1b[m\x1b[H\x1b[J\x1b[41m{}{}\x1b[5;5H", - format!("{}\r\n", "\x1b[X\x1b[C".repeat(80)).repeat(23), - "\x1b[X\x1b[C".repeat(80), + format!("{}\x1b[X\r\n", "\x1b[X\x1b[C".repeat(79)).repeat(23), + format!("{}\x1b[X", "\x1b[X\x1b[C".repeat(79)), ) .as_bytes() ); @@ -422,10 +420,8 @@ fn el() { assert_eq!( parser.screen().contents_formatted(), format!( - "\x1b[?25h\x1b[m\x1b[H\x1b[J{}{}\x1b[41m{}\x1b[5;5H", - "\r\n".repeat(4), - "\x1b[C".repeat(4), - "\x1b[X\x1b[C".repeat(76) + "\x1b[?25h\x1b[m\x1b[H\x1b[J\x1b[5;5H\x1b[41m{}\x1b[X\x1b[5;5H", + "\x1b[X\x1b[C".repeat(75) ) .as_bytes() ); @@ -464,9 +460,8 @@ fn el() { assert_eq!( parser.screen().contents_formatted(), format!( - "\x1b[?25h\x1b[m\x1b[H\x1b[J{}\x1b[41m{}\x1b[5;5H", - "\r\n".repeat(4), - "\x1b[X\x1b[C".repeat(5), + "\x1b[?25h\x1b[m\x1b[H\x1b[J\x1b[5;1H\x1b[41m{}\x1b[X", + "\x1b[X\x1b[C".repeat(4), ) .as_bytes() ); @@ -505,9 +500,8 @@ fn el() { assert_eq!( parser.screen().contents_formatted(), format!( - "\x1b[?25h\x1b[m\x1b[H\x1b[J{}\x1b[41m{}\x1b[5;5H", - "\r\n".repeat(4), - "\x1b[X\x1b[C".repeat(80), + "\x1b[?25h\x1b[m\x1b[H\x1b[J\x1b[5;1H\x1b[41m{}\x1b[X\x1b[5;5H", + "\x1b[X\x1b[C".repeat(79), ) .as_bytes() ); diff --git a/tests/escape.rs b/tests/escape.rs index 7a05491..1800ce8 100644 --- a/tests/escape.rs +++ b/tests/escape.rs @@ -153,20 +153,20 @@ fn decsc() { assert_eq!(parser.screen().cursor_position(), (4, 3)); assert_eq!( parser.screen().contents_formatted(), - b"\x1b[?25h\x1b[m\x1b[H\x1b[J\r\n\r\n\r\n\r\n\x1b[31mfoo" + b"\x1b[?25h\x1b[m\x1b[H\x1b[J\x1b[5;1H\x1b[31mfoo" ); parser.process(b"\x1b[32m\x1b[?6lbar"); assert_eq!(parser.screen().cursor_position(), (0, 3)); assert_eq!( parser.screen().contents_formatted(), - &b"\x1b[?25h\x1b[m\x1b[H\x1b[J\x1b[32mbar\r\n\r\n\r\n\r\n\x1b[31mfoo\x1b[1;4H"[..] + &b"\x1b[?25h\x1b[m\x1b[H\x1b[J\x1b[32mbar\x1b[5;1H\x1b[31mfoo\x1b[1;4H"[..] ); parser.process(b"\x1b8\x1b[Hz"); assert_eq!(parser.screen().cursor_position(), (4, 1)); assert_eq!( parser.screen().contents_formatted(), - &b"\x1b[?25h\x1b[m\x1b[H\x1b[J\x1b[32mbar\r\n\r\n\r\n\r\n\x1b[31mzoo\x1b[5;2H"[..] + &b"\x1b[?25h\x1b[m\x1b[H\x1b[J\x1b[32mbar\x1b[5;1H\x1b[31mzoo\x1b[5;2H"[..] ); } diff --git a/tests/scroll.rs b/tests/scroll.rs index 4bbe1e9..7c75c92 100644 --- a/tests/scroll.rs +++ b/tests/scroll.rs @@ -153,3 +153,21 @@ fn scrollback() { assert_eq!(parser.scroll_pos(), 7); assert_eq!(parser.screen().contents(), "10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33"); } + +#[test] +fn edge_of_screen() { + let mut parser = vt100::Parser::new(24, 80, 0); + let screen = parser.screen().clone(); + + parser.process(b"\x1b[31m\x1b[24;75Hfooba\x08r\x08\x1b[1@a"); + assert_eq!(parser.screen().cursor_position(), (23, 79)); + assert_eq!(parser.screen().contents(), "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n foobar"); + assert_eq!( + parser.screen().contents_formatted(), + &b"\x1b[?25h\x1b[m\x1b[H\x1b[J\x1b[24;75H\x1b[31mfoobar\x1b[24;80H"[..] + ); + assert_eq!( + parser.screen().contents_diff(&screen), + b"\x1b[24;75H\x1b[31mfoobar\x1b[24;80H" + ); +} diff --git a/tests/text.rs b/tests/text.rs index 3cd5a78..8578567 100644 --- a/tests/text.rs +++ b/tests/text.rs @@ -64,7 +64,7 @@ fn wide() { ); assert_eq!( parser.screen().contents_diff(&screen), - "\x1b[m\x1b[Haデbネ".as_bytes() + "\x1b[maデbネ".as_bytes() ); let screen = parser.screen().clone(); @@ -77,7 +77,7 @@ fn wide() { ); assert_eq!( parser.screen().contents_diff(&screen), - "\x1b[m\x1b[H\x1b[3Cc".as_bytes() + "\x1b[m\x1b[1;4Hc".as_bytes() ); let screen = parser.screen().clone(); @@ -90,7 +90,7 @@ fn wide() { ); assert_eq!( parser.screen().contents_diff(&screen), - "\x1b[m\x1b[H\x1b[6Cfoobar".as_bytes() + "\x1b[m\x1b[2Cfoobar".as_bytes() ); let screen = parser.screen().clone(); @@ -103,7 +103,7 @@ fn wide() { ); assert_eq!( parser.screen().contents_diff(&screen), - "\x1b[m\x1b[Hデcネfo\x1b[Cbar\x1b[X\x1b[C\x1b[1;12H".as_bytes() + "\x1b[m\x1b[Hデcネfo\x1b[Cbar\x1b[X".as_bytes() ); let screen = parser.screen().clone(); @@ -129,7 +129,7 @@ fn wide() { ); assert_eq!( parser.screen().contents_diff(&screen), - "\x1b[m\x1b[Hデcネfo\x1b[Cbar\x1b[X\x1b[C\x1b[1;12H".as_bytes() + "\x1b[m\x1b[Hデcネfo\x1b[Cbar\x1b[X".as_bytes() ); } diff --git a/tests/window_contents.rs b/tests/window_contents.rs index d7a3b13..0df62ef 100644 --- a/tests/window_contents.rs +++ b/tests/window_contents.rs @@ -71,7 +71,7 @@ fn empty_cells() { assert_eq!(parser.screen().contents(), "foo bar"); assert_eq!( parser.screen().contents_formatted(), - &b"\x1b[?25h\x1b[m\x1b[H\x1b[J\x1b[31mfoo\x1b[m\x1b[C\x1b[C\x1b[32m bar\x1b[1;4H"[..] + &b"\x1b[?25h\x1b[m\x1b[H\x1b[J\x1b[31mfoo\x1b[2C\x1b[32m bar\x1b[1;4H"[..] ); } @@ -87,7 +87,7 @@ fn cursor_positioning() { parser.screen().contents_formatted(), b"\x1b[?25h\x1b[m\x1b[H\x1b[J:" ); - assert_eq!(parser.screen().contents_diff(&screen1), b"\x1b[m\x1b[H:"); + assert_eq!(parser.screen().contents_diff(&screen1), b"\x1b[m:"); parser.process(b"a"); let screen3 = parser.screen().clone(); @@ -96,10 +96,7 @@ fn cursor_positioning() { parser.screen().contents_formatted(), b"\x1b[?25h\x1b[m\x1b[H\x1b[J:a" ); - assert_eq!( - parser.screen().contents_diff(&screen2), - b"\x1b[m\x1b[H\x1b[Ca" - ); + assert_eq!(parser.screen().contents_diff(&screen2), b"\x1b[ma"); parser.process(b"\x1b[1;2H\x1b[K"); assert_eq!(parser.screen().cursor_position(), (0, 1)); @@ -109,7 +106,7 @@ fn cursor_positioning() { ); assert_eq!( parser.screen().contents_diff(&screen3), - b"\x1b[m\x1b[H\x1b[C\x1b[X\x1b[C\x1b[1;2H" + b"\x1b[m\x1b[1;2H\x1b[X" ); } @@ -267,9 +264,7 @@ fn rows() { ] ); assert_eq!( - screen2 - .rows_formatted(0, 80) - .collect::>>(), + screen2.rows_formatted(0, 80).collect::>>(), vec![ b"\x1b[31mfoo".to_vec(), vec![], @@ -280,7 +275,7 @@ fn rows() { vec![], vec![], vec![], - b"\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[32mbar".to_vec(), + b"\x1b[9C\x1b[32mbar".to_vec(), vec![], vec![], vec![], @@ -290,7 +285,7 @@ fn rows() { vec![], vec![], vec![], - b"\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[33mbaz".to_vec(), + b"\x1b[19C\x1b[33mbaz".to_vec(), vec![], vec![], vec![], @@ -327,9 +322,7 @@ fn rows() { ] ); assert_eq!( - screen2 - .rows_formatted(5, 15) - .collect::>>(), + screen2.rows_formatted(5, 15).collect::>>(), vec![ vec![], vec![], @@ -340,7 +333,7 @@ fn rows() { vec![], vec![], vec![], - b"\x1b[C\x1b[C\x1b[C\x1b[C\x1b[32mbar".to_vec(), + b"\x1b[4C\x1b[32mbar".to_vec(), vec![], vec![], vec![], @@ -350,7 +343,7 @@ fn rows() { vec![], vec![], vec![], - b"\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[C\x1b[33mb".to_vec(), + b"\x1b[14C\x1b[33mb".to_vec(), vec![], vec![], vec![], @@ -429,7 +422,7 @@ fn diff() { let screen2 = parser.screen().clone(); assert_eq!( screen2.contents_diff(&screen1), - b"\x1b[m\x1b[H\x1b[5C\x1b[32m bar" + b"\x1b[m\x1b[5C\x1b[32m bar" ); compare_diff(&screen1, &screen2, b""); @@ -440,18 +433,12 @@ fn diff() { parser.process(b"\x1b[1;7H\x1b[32mbaz"); let screen4 = parser.screen().clone(); - assert_eq!( - screen4.contents_diff(&screen3), - b"\x1b[m\x1b[H\x1b[8C\x1b[32mz" - ); + assert_eq!(screen4.contents_diff(&screen3), b"\x1b[m\x1b[5C\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[m\x1b[H\x1b[7C\x1b[X\x1b[C\x1b[1;8H" - ); + assert_eq!(screen5.contents_diff(&screen4), b"\x1b[m\x1b[1;8H\x1b[X"); compare_diff( &screen4, &screen5, -- cgit v1.2.3-54-g00ecf