1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-24 13:52:55 +03:00

term: avoid dirtying lines when the cursor moves

Tag CursorPosition with the seqno of the cursor move instead.
This should avoid over-invalidating lines and the selection
if it was just the cursor that moved.
This commit is contained in:
Wez Furlong 2021-08-08 22:47:10 -07:00
parent 23097993e5
commit 80d261977f
7 changed files with 88 additions and 93 deletions

View File

@ -20,6 +20,7 @@ use anyhow::Error;
use serde::{Deserialize, Serialize};
use std::ops::{Deref, DerefMut, Range};
use std::str;
use termwiz::surface::SequenceNo;
pub mod config;
pub use config::TerminalConfiguration;
@ -112,6 +113,7 @@ pub struct CursorPosition {
pub y: VisibleRowIndex,
pub shape: termwiz::surface::CursorShape,
pub visibility: termwiz::surface::CursorVisibility,
pub seqno: SequenceNo,
}
#[cfg_attr(feature = "use_serde", derive(Deserialize, Serialize))]

View File

@ -254,6 +254,7 @@ impl Screen {
y: new_cursor_y,
shape: cursor.shape,
visibility: cursor.visibility,
seqno,
}
}

View File

@ -737,6 +737,7 @@ impl TerminalState {
} else {
CursorVisibility::Hidden
},
seqno: self.cursor.seqno,
}
}
@ -746,16 +747,10 @@ impl TerminalState {
/// Sets the cursor position to precisely the x and values provided
fn set_cursor_position_absolute(&mut self, x: usize, y: VisibleRowIndex) {
let seqno = self.seqno;
let old_y = self.cursor.y;
self.cursor.y = y;
self.cursor.x = x;
self.cursor.seqno = self.seqno;
self.wrap_next = false;
let screen = self.screen_mut();
screen.dirty_line(old_y, seqno);
screen.dirty_line(y, seqno);
}
/// Sets the cursor position. x and y are 0-based and relative to the
@ -925,8 +920,7 @@ impl TerminalState {
None => self.left_and_right_margins.end - 1,
};
self.cursor.x = x.min(self.left_and_right_margins.end - 1);
let y = self.cursor.y;
self.screen_mut().dirty_line(y, seqno);
self.cursor.seqno = seqno;
}
/// Move the cursor up 1 line. If the position is at the top scroll margin,
@ -1935,7 +1929,6 @@ impl TerminalState {
Cursor::Left(n) => {
// https://vt100.net/docs/vt510-rm/CUB.html
let y = self.cursor.y;
let candidate = self.cursor.x as i64 - n as i64;
let new_x = if self.cursor.x < self.left_and_right_margins.start {
// outside the margin, so allow movement to the border
@ -1957,14 +1950,12 @@ impl TerminalState {
let new_x = new_x.max(0) as usize;
self.cursor.x = new_x;
self.cursor.seqno = seqno;
self.wrap_next = false;
let screen = self.screen_mut();
screen.dirty_line(y, seqno);
}
Cursor::Right(n) => {
// https://vt100.net/docs/vt510-rm/CUF.html
let y = self.cursor.y;
let cols = self.screen().physical_cols;
let new_x = if self.cursor.x >= self.left_and_right_margins.end {
// outside the margin, so allow movement to screen edge
@ -1975,15 +1966,13 @@ impl TerminalState {
};
self.cursor.x = new_x;
self.cursor.seqno = seqno;
self.wrap_next = false;
let screen = self.screen_mut();
screen.dirty_line(y, seqno);
}
Cursor::Up(n) => {
// https://vt100.net/docs/vt510-rm/CUU.html
let old_y = self.cursor.y;
let candidate = self.cursor.y.saturating_sub(i64::from(n));
let new_y = if self.cursor.y < self.top_and_bottom_margins.start {
// above the top margin, so allow movement to
@ -2001,14 +1990,11 @@ impl TerminalState {
let new_y = new_y.max(0);
self.cursor.y = new_y;
self.cursor.seqno = seqno;
self.wrap_next = false;
let screen = self.screen_mut();
screen.dirty_line(old_y, seqno);
screen.dirty_line(new_y, seqno);
}
Cursor::Down(n) => {
// https://vt100.net/docs/vt510-rm/CUD.html
let old_y = self.cursor.y;
let rows = self.screen().physical_rows;
let new_y = if self.cursor.y >= self.top_and_bottom_margins.end {
// below the bottom margin, so allow movement to
@ -2020,10 +2006,8 @@ impl TerminalState {
};
self.cursor.y = new_y;
self.cursor.seqno = seqno;
self.wrap_next = false;
let screen = self.screen_mut();
screen.dirty_line(old_y, seqno);
screen.dirty_line(new_y, seqno);
}
Cursor::CharacterAndLinePosition { line, col } | Cursor::Position { line, col } => self
@ -2044,10 +2028,8 @@ impl TerminalState {
col
};
self.cursor.x = col.min(self.screen().physical_cols - 1);
self.cursor.seqno = seqno;
self.wrap_next = false;
let y = self.cursor.y;
let screen = self.screen_mut();
screen.dirty_line(y, seqno);
}
Cursor::CharacterPositionBackward(col) => self.set_cursor_pos(
@ -2070,7 +2052,6 @@ impl TerminalState {
}
Cursor::NextLine(n) => {
// https://vt100.net/docs/vt510-rm/CNL.html
let old_y = self.cursor.y;
let rows = self.screen().physical_rows;
let new_y = if self.cursor.y >= self.top_and_bottom_margins.end {
// below the bottom margin, so allow movement to
@ -2083,14 +2064,11 @@ impl TerminalState {
self.cursor.y = new_y;
self.cursor.x = self.left_and_right_margins.start;
self.cursor.seqno = seqno;
self.wrap_next = false;
let screen = self.screen_mut();
screen.dirty_line(old_y, seqno);
screen.dirty_line(new_y, seqno);
}
Cursor::PrecedingLine(n) => {
// https://vt100.net/docs/vt510-rm/CPL.html
let old_y = self.cursor.y;
let candidate = self.cursor.y.saturating_sub(i64::from(n));
let new_y = if self.cursor.y < self.top_and_bottom_margins.start {
// above the top margin, so allow movement to
@ -2109,10 +2087,8 @@ impl TerminalState {
self.cursor.y = new_y;
self.cursor.x = self.left_and_right_margins.start;
self.cursor.seqno = seqno;
self.wrap_next = false;
let screen = self.screen_mut();
screen.dirty_line(old_y, seqno);
screen.dirty_line(new_y, seqno);
}
Cursor::ActivePositionReport { .. } => {

View File

@ -5,11 +5,11 @@ use super::*;
#[test]
fn test_bs() {
let mut term = TestTerm::new(3, 4, 0);
term.assert_cursor_pos(0, 0, None);
term.assert_cursor_pos(0, 0, None, Some(0));
term.print("\x08");
term.assert_cursor_pos(0, 0, Some("cannot move left of the margin"));
term.assert_cursor_pos(0, 0, Some("cannot move left of the margin"), Some(0));
term.print("ab\x08");
term.assert_cursor_pos(1, 0, None);
term.assert_cursor_pos(1, 0, None, None);
// TODO: when we can set the left margin, we should test that here
}
@ -17,14 +17,22 @@ fn test_bs() {
fn test_lf() {
let mut term = TestTerm::new(3, 10, 0);
term.print("hello\n");
term.assert_cursor_pos(5, 1, Some("LF moves vertically only"));
term.assert_cursor_pos(5, 1, Some("LF moves vertically only"), None);
}
#[test]
fn test_cr() {
let mut term = TestTerm::new(3, 10, 0);
term.print("hello\r");
term.assert_cursor_pos(0, 0, Some("CR moves to left margin on current line"));
term.assert_cursor_pos(
0,
0,
Some(
"CR moves to left margin on current line, \
but is unchanged relative to the initial state",
),
Some(0),
);
// TODO: when we can set the left margin, we should test that here
}
@ -32,11 +40,11 @@ fn test_cr() {
fn test_tab() {
let mut term = TestTerm::new(3, 25, 0);
term.print("\t");
term.assert_cursor_pos(8, 0, None);
term.assert_cursor_pos(8, 0, None, None);
term.print("\t");
term.assert_cursor_pos(16, 0, None);
term.assert_cursor_pos(16, 0, None, None);
term.print("\t");
term.assert_cursor_pos(24, 0, None);
term.assert_cursor_pos(24, 0, None, None);
term.print("\t");
term.assert_cursor_pos(24, 0, None);
term.assert_cursor_pos(24, 0, None, None);
}

View File

@ -6,12 +6,12 @@ use super::*;
fn test_ind() {
let mut term = TestTerm::new(4, 4, 0);
term.print("a\r\nb\x1bD");
term.assert_cursor_pos(1, 2, None);
term.assert_cursor_pos(1, 2, None, None);
assert_visible_contents(&term, file!(), line!(), &["a", "b", "", ""]);
term.print("\x1bD");
term.assert_cursor_pos(1, 3, None);
term.assert_cursor_pos(1, 3, None, None);
term.print("\x1bD");
term.assert_cursor_pos(1, 3, None);
term.assert_cursor_pos(1, 3, None, Some(term.current_seqno() - 1));
assert_visible_contents(&term, file!(), line!(), &["b", "", "", ""]);
}
@ -19,11 +19,11 @@ fn test_ind() {
fn test_nel() {
let mut term = TestTerm::new(4, 4, 0);
term.print("a\r\nb\x1bE");
term.assert_cursor_pos(0, 2, None);
term.assert_cursor_pos(0, 2, None, None);
term.print("\x1bE");
term.assert_cursor_pos(0, 3, None);
term.assert_cursor_pos(0, 3, None, None);
term.print("\x1bE");
term.assert_cursor_pos(0, 3, None);
term.assert_cursor_pos(0, 3, None, None);
assert_visible_contents(&term, file!(), line!(), &["b", "", "", ""]);
}
@ -32,25 +32,25 @@ fn test_hts() {
let mut term = TestTerm::new(3, 25, 0);
term.print("boo");
term.print("\x1bH\r\n");
term.assert_cursor_pos(0, 1, None);
term.assert_cursor_pos(0, 1, None, None);
term.print("\t");
term.assert_cursor_pos(3, 1, None);
term.assert_cursor_pos(3, 1, None, None);
term.print("\t");
term.assert_cursor_pos(8, 1, None);
term.assert_cursor_pos(8, 1, None, None);
// Check that tabs are expanded if we resize
term.resize(4, 80, 4 * 16, 80 * 8);
term.cup(0, 1);
term.print("\t");
term.assert_cursor_pos(3, 1, None);
term.assert_cursor_pos(3, 1, None, None);
term.print("\t");
term.assert_cursor_pos(8, 1, None);
term.assert_cursor_pos(8, 1, None, None);
term.print("\t");
term.assert_cursor_pos(16, 1, None);
term.assert_cursor_pos(16, 1, None, None);
term.print("\t");
term.assert_cursor_pos(24, 1, None);
term.assert_cursor_pos(24, 1, None, None);
term.print("\t");
term.assert_cursor_pos(32, 1, None);
term.assert_cursor_pos(32, 1, None, None);
}
#[test]
@ -58,14 +58,15 @@ fn test_ri() {
let mut term = TestTerm::new(4, 2, 0);
term.print("a\r\nb\r\nc\r\nd.");
assert_visible_contents(&term, file!(), line!(), &["a", "b", "c", "d."]);
term.assert_cursor_pos(1, 3, None);
term.assert_cursor_pos(1, 3, None, None);
term.print("\x1bM");
term.assert_cursor_pos(1, 2, None);
term.assert_cursor_pos(1, 2, None, None);
term.print("\x1bM");
term.assert_cursor_pos(1, 1, None);
term.assert_cursor_pos(1, 1, None, None);
term.print("\x1bM");
term.assert_cursor_pos(1, 0, None);
term.assert_cursor_pos(1, 0, None, None);
let seqno = term.current_seqno();
term.print("\x1bM");
term.assert_cursor_pos(1, 0, None);
term.assert_cursor_pos(1, 0, None, Some(seqno));
assert_visible_contents(&term, file!(), line!(), &["", "a", "b", "c"]);
}

View File

@ -3,20 +3,20 @@ use super::*;
#[test]
fn test_vpa() {
let mut term = TestTerm::new(3, 4, 0);
term.assert_cursor_pos(0, 0, None);
term.assert_cursor_pos(0, 0, None, Some(0));
term.print("a\r\nb\r\nc");
term.assert_cursor_pos(1, 2, None);
term.assert_cursor_pos(1, 2, None, None);
term.print("\x1b[d");
term.assert_cursor_pos(1, 0, None);
term.assert_cursor_pos(1, 0, None, None);
term.print("\r\n\r\n");
term.assert_cursor_pos(0, 2, None);
term.assert_cursor_pos(0, 2, None, None);
// escapes are 1-based, so check that we're handling that
// when we parse them!
term.print("\x1b[2d");
term.assert_cursor_pos(0, 1, None);
term.assert_cursor_pos(0, 1, None, None);
term.print("\x1b[-2d");
term.assert_cursor_pos(0, 1, None);
term.assert_cursor_pos(0, 1, None, Some(term.current_seqno() - 1));
}
#[test]
@ -85,30 +85,30 @@ fn test_dch() {
fn test_cup() {
let mut term = TestTerm::new(3, 4, 0);
term.cup(1, 1);
term.assert_cursor_pos(1, 1, None);
term.assert_cursor_pos(1, 1, None, None);
term.cup(-1, -1);
term.assert_cursor_pos(0, 0, None);
term.assert_cursor_pos(0, 0, None, None);
term.cup(2, 2);
term.assert_cursor_pos(2, 2, None);
term.assert_cursor_pos(2, 2, None, None);
term.cup(-1, -1);
term.assert_cursor_pos(0, 0, None);
term.assert_cursor_pos(0, 0, None, None);
term.cup(500, 500);
term.assert_cursor_pos(3, 2, None);
term.assert_cursor_pos(3, 2, None, None);
}
#[test]
fn test_hvp() {
let mut term = TestTerm::new(3, 4, 0);
term.hvp(1, 1);
term.assert_cursor_pos(1, 1, None);
term.assert_cursor_pos(1, 1, None, None);
term.hvp(-1, -1);
term.assert_cursor_pos(0, 0, None);
term.assert_cursor_pos(0, 0, None, None);
term.hvp(2, 2);
term.assert_cursor_pos(2, 2, None);
term.assert_cursor_pos(2, 2, None, None);
term.hvp(-1, -1);
term.assert_cursor_pos(0, 0, None);
term.assert_cursor_pos(0, 0, None, None);
term.hvp(500, 500);
term.assert_cursor_pos(3, 2, None);
term.assert_cursor_pos(3, 2, None, None);
}
#[test]
@ -116,9 +116,10 @@ fn test_dl() {
let mut term = TestTerm::new(3, 1, 0);
term.print("a\r\nb\r\nc");
term.cup(0, 1);
let seqno = term.current_seqno();
term.delete_lines(1);
assert_visible_contents(&term, file!(), line!(), &["a", "c", ""]);
term.assert_cursor_pos(0, 1, None);
term.assert_cursor_pos(0, 1, None, Some(seqno));
term.cup(0, 0);
term.delete_lines(2);
assert_visible_contents(&term, file!(), line!(), &["", "", ""]);
@ -132,22 +133,23 @@ fn test_dl() {
fn test_cha() {
let mut term = TestTerm::new(3, 4, 0);
term.cup(1, 1);
term.assert_cursor_pos(1, 1, None);
term.assert_cursor_pos(1, 1, None, None);
term.print("\x1b[G");
term.assert_cursor_pos(0, 1, None);
term.assert_cursor_pos(0, 1, None, None);
term.print("\x1b[2G");
term.assert_cursor_pos(1, 1, None);
term.assert_cursor_pos(1, 1, None, None);
term.print("\x1b[0G");
term.assert_cursor_pos(0, 1, None);
term.assert_cursor_pos(0, 1, None, None);
let seqno = term.current_seqno();
term.print("\x1b[-1G");
term.assert_cursor_pos(0, 1, None);
term.assert_cursor_pos(0, 1, None, Some(seqno));
term.print("\x1b[100G");
term.assert_cursor_pos(3, 1, None);
term.assert_cursor_pos(3, 1, None, None);
}
#[test]

View File

@ -149,13 +149,14 @@ impl TestTerm {
self.print("!p");
}
fn assert_cursor_pos(&self, x: usize, y: i64, reason: Option<&str>) {
fn assert_cursor_pos(&self, x: usize, y: i64, reason: Option<&str>, seqno: Option<SequenceNo>) {
let cursor = self.cursor_pos();
let expect = CursorPosition {
x,
y,
shape: CursorShape::Default,
visibility: CursorVisibility::Visible,
seqno: seqno.unwrap_or_else(|| self.current_seqno()),
};
assert_eq!(
cursor, expect,
@ -165,12 +166,14 @@ impl TestTerm {
}
fn assert_dirty_lines(&self, seqno: SequenceNo, expected: &[usize], reason: Option<&str>) {
let mut seqs = vec![];
let dirty_indices: Vec<usize> = self
.screen()
.lines
.iter()
.enumerate()
.filter_map(|(i, line)| {
seqs.push(line.current_seqno());
if line.changed_since(seqno) {
Some(i)
} else {
@ -180,8 +183,9 @@ impl TestTerm {
.collect();
assert_eq!(
&dirty_indices, &expected,
"actual dirty lines (left) didn't match expected dirty lines (right) reason={:?}",
reason
"actual dirty lines (left) didn't match expected dirty \
lines (right) reason={:?}. threshold seq: {} seqs: {:?}",
reason, seqno, seqs
);
}
}
@ -503,14 +507,14 @@ fn cursor_movement_damage() {
let seqno = term.current_seqno();
term.print("fooo.");
assert_visible_contents(&term, file!(), line!(), &["foo", "o."]);
term.assert_cursor_pos(2, 1, None);
term.assert_cursor_pos(2, 1, None, None);
term.assert_dirty_lines(seqno, &[0, 1], None);
term.cup(0, 1);
let seqno = term.current_seqno();
term.print("\x08");
term.assert_cursor_pos(0, 1, Some("BS doesn't change the line"));
term.assert_cursor_pos(0, 1, Some("BS doesn't change the line"), Some(seqno));
// Since we didn't move, the line isn't dirty
term.assert_dirty_lines(seqno, &[], None);
@ -518,9 +522,10 @@ fn cursor_movement_damage() {
term.cup(0, 0);
term.assert_dirty_lines(
seqno,
&[0, 1],
Some("cursor movement dirties old and new lines"),
&[],
Some("cursor movement no longer dirties old and new lines"),
);
term.assert_cursor_pos(0, 0, None, None);
}
const NUM_COLS: usize = 3;
@ -664,7 +669,7 @@ fn test_delete_lines() {
term.delete_lines(2);
assert_visible_contents(&term, file!(), line!(), &["111", "aaa", "", "", "bbb"]);
term.assert_dirty_lines(seqno, &[0, 1, 2, 3], None);
term.assert_dirty_lines(seqno, &[1, 2, 3], None);
// expand the scroll region to fill the screen
term.set_scroll_region(0, 4);