model: replace LineBits::DIRTY with a sequence number

Terminal now maintains a sequence number that increments
for each Action that is applied to it.

Changes to lines are tagged with the current sequence number.

This makes it a bit easier to reason about when an individual
line has changed relative to some point in "time"; the consumer
of the terminal can sample the current sequence number and then
can later determine which lines have changed since that point
in time.

refs: https://github.com/wez/wezterm/issues/867
This commit is contained in:
Wez Furlong 2021-08-08 12:45:08 -07:00
parent 2e158f58f3
commit eae327efcc
28 changed files with 477 additions and 289 deletions

1
Cargo.lock generated
View File

@ -5088,6 +5088,7 @@ dependencies = [
"rangeset",
"rcgen",
"smol",
"termwiz",
"uds_windows",
"url",
"wezterm-term",

View File

@ -28,7 +28,7 @@ use std::io::Cursor;
use std::ops::Range;
use std::sync::Arc;
use termwiz::hyperlink::Hyperlink;
use termwiz::surface::Line;
use termwiz::surface::{Line, SequenceNo};
use varbincode;
use wezterm_term::color::ColorPalette;
use wezterm_term::{Alert, ClipboardSelection, StableRowIndex};
@ -401,7 +401,7 @@ macro_rules! pdu {
/// The overall version of the codec.
/// This must be bumped when backwards incompatible changes
/// are made to the types and protocol.
pub const CODEC_VERSION: usize = 8;
pub const CODEC_VERSION: usize = 9;
// Defines the Pdu enum.
// Each struct has an explicit identifying number.
@ -729,6 +729,7 @@ pub struct GetPaneRenderChangesResponse {
pub bonus_lines: SerializedLines,
pub input_serial: Option<InputSerial>,
pub seqno: SequenceNo,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]

View File

@ -20,7 +20,7 @@ use std::ops::Range;
use std::os::windows::io::{AsRawHandle, RawHandle};
use std::sync::Arc;
use termwiz::escape::DeviceControlMode;
use termwiz::surface::Line;
use termwiz::surface::{Line, SequenceNo, SEQ_ZERO};
use url::Url;
use wezterm_term::color::ColorPalette;
use wezterm_term::{
@ -65,8 +65,16 @@ impl Pane for LocalPane {
cursor
}
fn get_dirty_lines(&self, lines: Range<StableRowIndex>) -> RangeSet<StableRowIndex> {
terminal_get_dirty_lines(&mut self.terminal.borrow_mut(), lines)
fn get_current_seqno(&self) -> SequenceNo {
self.terminal.borrow().current_seqno()
}
fn get_changed_since(
&self,
lines: Range<StableRowIndex>,
seqno: SequenceNo,
) -> RangeSet<StableRowIndex> {
terminal_get_dirty_lines(&mut self.terminal.borrow_mut(), lines, seqno)
}
fn get_lines(&self, lines: Range<StableRowIndex>) -> (StableRowIndex, Vec<Line>) {
@ -81,6 +89,7 @@ impl Pane for LocalPane {
0,
"This pane is running tmux control mode. Press q to detach.",
CellAttributes::default(),
SEQ_ZERO,
);
}
}

View File

@ -12,7 +12,7 @@ use std::collections::HashMap;
use std::ops::Range;
use std::sync::{Arc, Mutex};
use termwiz::hyperlink::Rule;
use termwiz::surface::Line;
use termwiz::surface::{Line, SequenceNo, SEQ_ZERO};
use url::Url;
use wezterm_term::color::ColorPalette;
use wezterm_term::{
@ -128,7 +128,7 @@ impl LogicalLine {
}
pub fn apply_hyperlink_rules(&mut self, rules: &[Rule]) {
self.logical.invalidate_implicit_hyperlinks();
self.logical.invalidate_implicit_hyperlinks(SEQ_ZERO);
self.logical.scan_and_create_hyperlinks(rules);
if !self.logical.has_hyperlink() {
return;
@ -143,7 +143,7 @@ impl LogicalLine {
*phys = line;
line = remainder;
let wrapped = idx == num_phys - 1;
phys.set_last_cell_was_wrapped(wrapped);
phys.set_last_cell_was_wrapped(wrapped, SEQ_ZERO);
}
}
}
@ -157,9 +157,15 @@ pub trait Pane: Downcast {
/// the visible screen
fn get_cursor_position(&self) -> StableCursorPosition;
fn get_current_seqno(&self) -> SequenceNo;
/// Given a range of lines, return the subset of those lines that
/// have their dirty flag set to true.
fn get_dirty_lines(&self, lines: Range<StableRowIndex>) -> RangeSet<StableRowIndex>;
/// have changed since the supplied sequence no.
fn get_changed_since(
&self,
lines: Range<StableRowIndex>,
seqno: SequenceNo,
) -> RangeSet<StableRowIndex>;
/// Returns a set of lines from the scrollback or visible portion of
/// the display. The lines are indexed using StableRowIndex, which
@ -240,8 +246,8 @@ pub trait Pane: Downcast {
if prior.logical.last_cell_was_wrapped()
&& prior.logical.cells().len() <= MAX_LOGICAL_LINE_LEN
{
prior.logical.set_last_cell_was_wrapped(false);
prior.logical.append_line(line.clone());
prior.logical.set_last_cell_was_wrapped(false, SEQ_ZERO);
prior.logical.append_line(line.clone(), SEQ_ZERO);
prior.physical_lines.push(line);
} else {
let logical = line.clone();
@ -384,7 +390,16 @@ mod test {
fn get_cursor_position(&self) -> StableCursorPosition {
unimplemented!()
}
fn get_dirty_lines(&self, _: Range<StableRowIndex>) -> RangeSet<StableRowIndex> {
fn get_current_seqno(&self) -> SequenceNo {
unimplemented!()
}
fn get_changed_since(
&self,
_: Range<StableRowIndex>,
_: SequenceNo,
) -> RangeSet<StableRowIndex> {
unimplemented!()
}
fn get_lines(&self, lines: Range<StableRowIndex>) -> (StableRowIndex, Vec<Line>) {
@ -462,7 +477,7 @@ mod test {
for (idx, chunk) in chunks.into_iter().enumerate() {
let mut line = Line::from_text(&chunk, &Default::default());
if idx < n_chunks - 1 {
line.set_last_cell_was_wrapped(true);
line.set_last_cell_was_wrapped(true, SEQ_ZERO);
}
physical_lines.push(line);
}

View File

@ -2,6 +2,7 @@ use luahelper::impl_lua_conversion;
use rangeset::RangeSet;
use serde::{Deserialize, Serialize};
use std::ops::Range;
use termwiz::surface::{SequenceNo, SEQ_ZERO};
use wezterm_term::{Line, StableRowIndex, Terminal};
/// Describes the location of the cursor
@ -51,6 +52,7 @@ pub fn terminal_get_cursor_position(term: &mut Terminal) -> StableCursorPosition
pub fn terminal_get_dirty_lines(
term: &mut Terminal,
lines: Range<StableRowIndex>,
seqno: SequenceNo,
) -> RangeSet<StableRowIndex> {
let screen = term.screen();
let phys = screen.stable_range(&lines);
@ -62,7 +64,7 @@ pub fn terminal_get_dirty_lines(
.skip(phys.start)
.take(phys.end - phys.start)
{
if line.is_dirty() {
if line.changed_since(seqno) {
set.add(screen.phys_to_stable_row_index(idx))
}
}
@ -86,8 +88,7 @@ pub fn terminal_get_lines(
.take(phys_range.end - phys_range.start)
.map(|line| {
let mut cloned = line.clone();
line.clear_dirty();
cloned.set_reverse(reverse);
cloned.set_reverse(reverse, SEQ_ZERO);
cloned
})
.collect(),

View File

@ -1534,6 +1534,7 @@ mod test {
use crate::renderable::*;
use rangeset::RangeSet;
use std::ops::Range;
use termwiz::surface::SequenceNo;
use url::Url;
use wezterm_term::color::ColorPalette;
use wezterm_term::{KeyCode, KeyModifiers, Line, MouseEvent, StableRowIndex};
@ -1561,7 +1562,15 @@ mod test {
unimplemented!();
}
fn get_dirty_lines(&self, _lines: Range<StableRowIndex>) -> RangeSet<StableRowIndex> {
fn get_current_seqno(&self) -> SequenceNo {
unimplemented!();
}
fn get_changed_since(
&self,
_lines: Range<StableRowIndex>,
_: SequenceNo,
) -> RangeSet<StableRowIndex> {
unimplemented!();
}

View File

@ -26,8 +26,7 @@ use std::sync::Arc;
use std::time::Duration;
use termwiz::input::{InputEvent, KeyEvent, Modifiers, MouseEvent as TermWizMouseEvent};
use termwiz::render::terminfo::TerminfoRenderer;
use termwiz::surface::Change;
use termwiz::surface::Line;
use termwiz::surface::{Change, Line, SequenceNo};
use termwiz::terminal::{ScreenSize, TerminalWaker};
use termwiz::Context;
use url::Url;
@ -139,8 +138,16 @@ impl Pane for TermWizTerminalPane {
terminal_get_cursor_position(&mut self.terminal.borrow_mut())
}
fn get_dirty_lines(&self, lines: Range<StableRowIndex>) -> RangeSet<StableRowIndex> {
terminal_get_dirty_lines(&mut self.terminal.borrow_mut(), lines)
fn get_current_seqno(&self) -> SequenceNo {
self.terminal.borrow().current_seqno()
}
fn get_changed_since(
&self,
lines: Range<StableRowIndex>,
seqno: SequenceNo,
) -> RangeSet<StableRowIndex> {
terminal_get_dirty_lines(&mut self.terminal.borrow_mut(), lines, seqno)
}
fn get_lines(&self, lines: Range<StableRowIndex>) -> (StableRowIndex, Vec<Line>) {

View File

@ -3,6 +3,7 @@ use super::*;
use log::debug;
use std::collections::VecDeque;
use std::sync::Arc;
use termwiz::surface::SequenceNo;
/// Holds the model of a screen. This can either be the primary screen
/// which includes lines of scrollback text, or the alternate screen
@ -85,6 +86,7 @@ impl Screen {
physical_rows: usize,
cursor_x: usize,
cursor_y: PhysRowIndex,
seqno: SequenceNo,
) -> (usize, PhysRowIndex) {
let mut rewrapped = VecDeque::new();
let mut logical_line: Option<Line> = None;
@ -92,12 +94,12 @@ impl Screen {
let mut adjusted_cursor = (cursor_y, cursor_y);
for (phys_idx, mut line) in self.lines.drain(..).enumerate() {
line.invalidate_implicit_hyperlinks();
line.set_dirty();
line.invalidate_implicit_hyperlinks(seqno);
line.update_last_change_seqno(seqno);
let was_wrapped = line.last_cell_was_wrapped();
if was_wrapped {
line.set_last_cell_was_wrapped(false);
line.set_last_cell_was_wrapped(false, seqno);
}
let line = match logical_line.take() {
@ -111,7 +113,7 @@ impl Screen {
if phys_idx == cursor_y {
logical_cursor_x = Some(cursor_x + prior.cells().len());
}
prior.append_line(line);
prior.append_line(line, seqno);
prior
}
};
@ -130,7 +132,7 @@ impl Screen {
if line.cells().len() <= physical_cols {
rewrapped.push_back(line);
} else {
for line in line.wrap(physical_cols) {
for line in line.wrap(physical_cols, seqno) {
rewrapped.push_back(line);
}
}
@ -158,6 +160,7 @@ impl Screen {
physical_rows: usize,
physical_cols: usize,
cursor: CursorPosition,
seqno: SequenceNo,
) -> CursorPosition {
let physical_rows = physical_rows.max(1);
let physical_cols = physical_cols.max(1);
@ -185,15 +188,15 @@ impl Screen {
// screen (hence the check for allow_scrollback), to avoid
// conflicting screen updates with full screen apps.
if self.allow_scrollback {
self.rewrap_lines(physical_cols, physical_rows, cursor.x, cursor_phys)
self.rewrap_lines(physical_cols, physical_rows, cursor.x, cursor_phys, seqno)
} else {
for line in &mut self.lines {
if physical_cols < self.physical_cols {
// Do a simple prune of the lines instead
line.resize(physical_cols);
line.resize(physical_cols, seqno);
} else {
// otherwise: invalidate them
line.set_dirty();
line.update_last_change_seqno(seqno);
}
}
(cursor.x, cursor_phys)
@ -262,10 +265,10 @@ impl Screen {
/// Sets a line dirty. The line is relative to the visible origin.
#[inline]
pub fn dirty_line(&mut self, idx: VisibleRowIndex) {
pub fn dirty_line(&mut self, idx: VisibleRowIndex, seqno: SequenceNo) {
let line_idx = self.phys_row(idx);
if line_idx < self.lines.len() {
self.lines[line_idx].set_dirty();
self.lines[line_idx].update_last_change_seqno(seqno);
}
}
@ -289,33 +292,52 @@ impl Screen {
self.lines.iter().map(|l| l.clone()).collect()
}
pub fn insert_cell(&mut self, x: usize, y: VisibleRowIndex, right_margin: usize) {
pub fn insert_cell(
&mut self,
x: usize,
y: VisibleRowIndex,
right_margin: usize,
seqno: SequenceNo,
) {
let phys_cols = self.physical_cols;
let line_idx = self.phys_row(y);
let line = self.line_mut(line_idx);
line.insert_cell(x, Cell::default(), right_margin);
line.update_last_change_seqno(seqno);
line.insert_cell(x, Cell::default(), right_margin, seqno);
if line.cells().len() > phys_cols {
// Don't allow the line width to grow beyond
// the physical width
line.resize(phys_cols);
line.resize(phys_cols, seqno);
}
}
pub fn erase_cell(&mut self, x: usize, y: VisibleRowIndex, right_margin: usize) {
pub fn erase_cell(
&mut self,
x: usize,
y: VisibleRowIndex,
right_margin: usize,
seqno: SequenceNo,
) {
let line_idx = self.phys_row(y);
let line = self.line_mut(line_idx);
line.erase_cell_with_margin(x, right_margin);
line.erase_cell_with_margin(x, right_margin, seqno);
}
/// Set a cell. the x and y coordinates are relative to the visible screeen
/// origin. 0,0 is the top left.
pub fn set_cell(&mut self, x: usize, y: VisibleRowIndex, cell: &Cell) -> &Cell {
pub fn set_cell(
&mut self,
x: usize,
y: VisibleRowIndex,
cell: &Cell,
seqno: SequenceNo,
) -> &Cell {
let line_idx = self.phys_row(y);
//debug!("set_cell x={} y={} phys={} {:?}", x, y, line_idx, cell);
let line = self.line_mut(line_idx);
line.set_cell(x, cell.clone())
line.set_cell(x, cell.clone(), seqno)
}
pub fn cell_mut(&mut self, x: usize, y: VisibleRowIndex) -> Option<&mut Cell> {
@ -330,10 +352,16 @@ impl Screen {
line.cells().get(x)
}
pub fn clear_line(&mut self, y: VisibleRowIndex, cols: Range<usize>, attr: &CellAttributes) {
pub fn clear_line(
&mut self,
y: VisibleRowIndex,
cols: Range<usize>,
attr: &CellAttributes,
seqno: SequenceNo,
) {
let line_idx = self.phys_row(y);
let line = self.line_mut(line_idx);
line.fill_range(cols, &Cell::new(' ', attr.clone()));
line.fill_range(cols, &Cell::new(' ', attr.clone()), seqno);
}
/// Translate a VisibleRowIndex into a PhysRowIndex. The resultant index
@ -425,6 +453,7 @@ impl Screen {
scroll_region: &Range<VisibleRowIndex>,
left_and_right_margins: &Range<usize>,
num_rows: usize,
seqno: SequenceNo,
) {
log::debug!(
"scroll_up_within_margins region:{:?} margins:{:?} rows={}",
@ -434,7 +463,7 @@ impl Screen {
);
if left_and_right_margins.start == 0 && left_and_right_margins.end == self.physical_cols {
return self.scroll_up(scroll_region, num_rows);
return self.scroll_up(scroll_region, num_rows, seqno);
}
// Need to do the slower, more complex left and right bounded scroll
@ -462,12 +491,12 @@ impl Screen {
// and place them into the dest
let dest_row = self.line_mut(dest_row);
dest_row.set_dirty();
dest_row.invalidate_implicit_hyperlinks();
dest_row.update_last_change_seqno(seqno);
dest_row.invalidate_implicit_hyperlinks(seqno);
let dest_range =
left_and_right_margins.start..left_and_right_margins.start + cells.len();
if dest_row.cells().len() < dest_range.end {
dest_row.resize(dest_range.end);
dest_row.resize(dest_range.end, seqno);
}
let tail_range = dest_range.end..left_and_right_margins.end;
@ -478,15 +507,15 @@ impl Screen {
*dest_cell = src_cell.clone();
}
dest_row.fill_range(tail_range, &Cell::default());
dest_row.fill_range(tail_range, &Cell::default(), seqno);
}
}
// and blank out rows at the bottom
for n in phys_scroll.start + rows_to_copy..phys_scroll.end {
let dest_row = self.line_mut(n);
dest_row.set_dirty();
dest_row.invalidate_implicit_hyperlinks();
dest_row.update_last_change_seqno(seqno);
dest_row.invalidate_implicit_hyperlinks(seqno);
for cell in dest_row
.cells_mut()
.iter_mut()
@ -512,7 +541,12 @@ impl Screen {
/// at bottom.
/// If the top of the region is the top of the visible display, rather than
/// removing the lines we let them go into the scrollback.
pub fn scroll_up(&mut self, scroll_region: &Range<VisibleRowIndex>, num_rows: usize) {
pub fn scroll_up(
&mut self,
scroll_region: &Range<VisibleRowIndex>,
num_rows: usize,
seqno: SequenceNo,
) {
let phys_scroll = self.phys_range(scroll_region);
let num_rows = num_rows.min(phys_scroll.end - phys_scroll.start);
@ -528,7 +562,7 @@ impl Screen {
// so we use the scroll region bounds to gate the invalidation.
if scroll_region.start != 0 || scroll_region.end as usize != self.physical_rows {
for y in phys_scroll.clone() {
self.line_mut(y).set_dirty();
self.line_mut(y).update_last_change_seqno(seqno);
}
}
@ -560,8 +594,8 @@ impl Screen {
for _ in 0..to_move {
let mut line = self.lines.remove(remove_idx).unwrap();
// Make the line like a new one of the appropriate width
line.resize_and_clear(0);
line.set_dirty();
line.resize_and_clear(0, seqno);
line.update_last_change_seqno(seqno);
if scroll_region.end as usize == self.physical_rows {
self.lines.push_back(line);
} else {
@ -615,7 +649,12 @@ impl Screen {
/// beyond the bottom get removed from the screen.
/// In other words, we remove (bottom-num_rows..bottom) and then insert
/// num_rows at scroll_top.
pub fn scroll_down(&mut self, scroll_region: &Range<VisibleRowIndex>, num_rows: usize) {
pub fn scroll_down(
&mut self,
scroll_region: &Range<VisibleRowIndex>,
num_rows: usize,
seqno: SequenceNo,
) {
debug!("scroll_down {:?} {}", scroll_region, num_rows);
let phys_scroll = self.phys_range(scroll_region);
let num_rows = num_rows.min(phys_scroll.end - phys_scroll.start);
@ -624,7 +663,7 @@ impl Screen {
// dirty the rows in the region
for y in phys_scroll.start..middle {
self.line_mut(y).set_dirty();
self.line_mut(y).update_last_change_seqno(seqno);
}
for _ in 0..num_rows {
@ -641,9 +680,10 @@ impl Screen {
scroll_region: &Range<VisibleRowIndex>,
left_and_right_margins: &Range<usize>,
num_rows: usize,
seqno: SequenceNo,
) {
if left_and_right_margins.start == 0 && left_and_right_margins.end == self.physical_cols {
return self.scroll_down(scroll_region, num_rows);
return self.scroll_down(scroll_region, num_rows, seqno);
}
// Need to do the slower, more complex left and right bounded scroll
@ -671,12 +711,12 @@ impl Screen {
// and place them into the dest
let dest_row = self.line_mut(dest_row);
dest_row.set_dirty();
dest_row.invalidate_implicit_hyperlinks();
dest_row.update_last_change_seqno(seqno);
dest_row.invalidate_implicit_hyperlinks(seqno);
let dest_range =
left_and_right_margins.start..left_and_right_margins.start + cells.len();
if dest_row.cells().len() < dest_range.end {
dest_row.resize(dest_range.end);
dest_row.resize(dest_range.end, seqno);
}
let tail_range = dest_range.end..left_and_right_margins.end;
@ -686,15 +726,15 @@ impl Screen {
*dest_cell = src_cell.clone();
}
dest_row.fill_range(tail_range, &Cell::default());
dest_row.fill_range(tail_range, &Cell::default(), seqno);
}
}
// and blank out rows at the top
for n in phys_scroll.start..phys_scroll.start + num_rows {
let dest_row = self.line_mut(n);
dest_row.set_dirty();
dest_row.invalidate_implicit_hyperlinks();
dest_row.update_last_change_seqno(seqno);
dest_row.invalidate_implicit_hyperlinks(seqno);
for cell in dest_row
.cells_mut()
.iter_mut()

View File

@ -123,6 +123,7 @@ impl Terminal {
/// characters; it is valid to feed in chunks of data as they arrive.
/// The output is parsed and applied to the terminal model.
pub fn advance_bytes<B: AsRef<[u8]>>(&mut self, bytes: B) {
self.state.increment_seqno();
let bytes = bytes.as_ref();
let mut performer = Performer::new(&mut self.state);
@ -131,6 +132,7 @@ impl Terminal {
}
pub fn perform_actions(&mut self, actions: Vec<termwiz::escape::Action>) {
self.state.increment_seqno();
let mut performer = Performer::new(&mut self.state);
for action in actions {
performer.perform(action);

View File

@ -58,6 +58,7 @@ pub enum ImageAttachStyle {
impl TerminalState {
pub(crate) fn assign_image_to_cells(&mut self, params: ImageAttachParams) -> PlacementInfo {
let seqno = self.seqno;
let physical_cols = self.screen().physical_cols;
let physical_rows = self.screen().physical_rows;
let cell_pixel_width = self.pixel_width / physical_cols;
@ -143,7 +144,8 @@ impl TerminalState {
}
};
self.screen_mut().set_cell(cursor_x + x, cursor_y, &cell);
self.screen_mut()
.set_cell(cursor_x + x, cursor_y, &cell, seqno);
xpos += x_delta;
}
ypos += y_delta;

View File

@ -292,6 +292,7 @@ impl TerminalState {
placement_id: Option<u32>,
info: PlacementInfo,
) {
let seqno = self.seqno;
let screen = self.screen_mut();
let range =
screen.stable_range(&(info.first_row..info.first_row + info.rows as StableRowIndex));
@ -301,7 +302,7 @@ impl TerminalState {
c.attrs_mut()
.detach_image_with_placement(image_id, placement_id);
}
line.set_dirty();
line.update_last_change_seqno(seqno);
}
}

View File

@ -16,7 +16,7 @@ use termwiz::escape::csi::{
};
use termwiz::escape::{OneBased, OperatingSystemCommand, CSI};
use termwiz::image::ImageData;
use termwiz::surface::{CursorShape, CursorVisibility};
use termwiz::surface::{CursorShape, CursorVisibility, SequenceNo};
use url::Url;
mod image;
@ -179,9 +179,14 @@ impl ScreenOrAlt {
physical_rows: usize,
physical_cols: usize,
cursor: CursorPosition,
seqno: SequenceNo,
) -> CursorPosition {
let cursor_main = self.screen.resize(physical_rows, physical_cols, cursor);
let cursor_alt = self.alt_screen.resize(physical_rows, physical_cols, cursor);
let cursor_main = self
.screen
.resize(physical_rows, physical_cols, cursor, seqno);
let cursor_alt = self
.alt_screen
.resize(physical_rows, physical_cols, cursor, seqno);
if self.alt_screen_is_active {
cursor_alt
} else {
@ -189,24 +194,26 @@ impl ScreenOrAlt {
}
}
pub fn activate_alt_screen(&mut self) {
pub fn activate_alt_screen(&mut self, seqno: SequenceNo) {
self.alt_screen_is_active = true;
self.dirty_top_phys_rows();
self.dirty_top_phys_rows(seqno);
}
pub fn activate_primary_screen(&mut self) {
pub fn activate_primary_screen(&mut self, seqno: SequenceNo) {
self.alt_screen_is_active = false;
self.dirty_top_phys_rows();
self.dirty_top_phys_rows(seqno);
}
// When switching between alt and primary screen, we implicitly change
// the content associated with StableRowIndex 0..num_rows. The muxer
// use case needs to know to invalidate its cache, so we mark those rows
// as dirty.
fn dirty_top_phys_rows(&mut self) {
fn dirty_top_phys_rows(&mut self, seqno: SequenceNo) {
let num_rows = self.screen.physical_rows;
for line_idx in 0..num_rows {
self.screen.line_mut(line_idx).set_dirty();
self.screen
.line_mut(line_idx)
.update_last_change_seqno(seqno);
}
}
@ -333,6 +340,7 @@ pub struct TerminalState {
user_vars: HashMap<String, String>,
kitty_img: KittyImageState,
seqno: SequenceNo,
}
fn default_color_map() -> HashMap<u16, RgbColor> {
@ -458,9 +466,18 @@ impl TerminalState {
image_cache: lru::LruCache::new(16),
user_vars: HashMap::new(),
kitty_img: Default::default(),
seqno: 0,
}
}
pub fn current_seqno(&self) -> SequenceNo {
self.seqno
}
pub fn increment_seqno(&mut self) {
self.seqno += 1;
}
pub fn set_config(&mut self, config: Arc<dyn TerminalConfiguration>) {
self.config = config;
}
@ -685,9 +702,9 @@ impl TerminalState {
pixel_width: usize,
pixel_height: usize,
) {
let adjusted_cursor = self
.screen
.resize(physical_rows, physical_cols, self.cursor);
let adjusted_cursor =
self.screen
.resize(physical_rows, physical_cols, self.cursor, self.seqno);
self.top_and_bottom_margins = 0..physical_rows as i64;
self.left_and_right_margins = 0..physical_cols;
self.pixel_height = pixel_height;
@ -699,19 +716,12 @@ impl TerminalState {
);
}
/// Clear the dirty flag for all dirty lines
pub fn clean_dirty_lines(&mut self) {
let screen = self.screen_mut();
for line in &mut screen.lines {
line.clear_dirty();
}
}
/// When dealing with selection, mark a range of lines as dirty
pub fn make_all_lines_dirty(&mut self) {
let seqno = self.seqno;
let screen = self.screen_mut();
for line in &mut screen.lines {
line.set_dirty();
line.update_last_change_seqno(seqno);
}
}
@ -736,6 +746,7 @@ 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;
@ -743,8 +754,8 @@ impl TerminalState {
self.wrap_next = false;
let screen = self.screen_mut();
screen.dirty_line(old_y);
screen.dirty_line(y);
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
@ -806,22 +817,26 @@ impl TerminalState {
}
fn scroll_up(&mut self, num_rows: usize) {
let seqno = self.seqno;
let top_and_bottom_margins = self.top_and_bottom_margins.clone();
let left_and_right_margins = self.left_and_right_margins.clone();
self.screen_mut().scroll_up_within_margins(
&top_and_bottom_margins,
&left_and_right_margins,
num_rows,
seqno,
)
}
fn scroll_down(&mut self, num_rows: usize) {
let seqno = self.seqno;
let top_and_bottom_margins = self.top_and_bottom_margins.clone();
let left_and_right_margins = self.left_and_right_margins.clone();
self.screen_mut().scroll_down_within_margins(
&top_and_bottom_margins,
&left_and_right_margins,
num_rows,
seqno,
)
}
@ -904,13 +919,14 @@ impl TerminalState {
/// the cursor moves to the right margin. HT does not cause text to auto
/// wrap.
fn c0_horizontal_tab(&mut self) {
let seqno = self.seqno;
let x = match self.tabs.find_next_tab_stop(self.cursor.x) {
Some(x) => x,
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);
self.screen_mut().dirty_line(y, seqno);
}
/// Move the cursor up 1 line. If the position is at the top scroll margin,
@ -1009,9 +1025,9 @@ impl TerminalState {
self.application_keypad = false;
self.top_and_bottom_margins = 0..self.screen().physical_rows as i64;
self.left_and_right_margins = 0..self.screen().physical_cols;
self.screen.activate_alt_screen();
self.screen.activate_alt_screen(self.seqno);
self.screen.saved_cursor().take();
self.screen.activate_primary_screen();
self.screen.activate_primary_screen(self.seqno);
self.screen.saved_cursor().take();
self.kitty_remove_all_placements(true);
@ -1329,7 +1345,7 @@ impl TerminalState {
DecPrivateModeCode::EnableAlternateScreen,
)) => {
if !self.screen.is_alt_screen_active() {
self.screen.activate_alt_screen();
self.screen.activate_alt_screen(self.seqno);
self.pen = CellAttributes::default();
}
}
@ -1339,7 +1355,7 @@ impl TerminalState {
if self.screen.is_alt_screen_active() {
self.pen = CellAttributes::default();
self.erase_in_display(EraseInDisplay::EraseDisplay);
self.screen.activate_primary_screen();
self.screen.activate_primary_screen(self.seqno);
}
}
@ -1347,7 +1363,7 @@ impl TerminalState {
DecPrivateModeCode::EnableAlternateScreen,
)) => {
if self.screen.is_alt_screen_active() {
self.screen.activate_primary_screen();
self.screen.activate_primary_screen(self.seqno);
self.pen = CellAttributes::default();
}
}
@ -1496,7 +1512,7 @@ impl TerminalState {
)) => {
if !self.screen.is_alt_screen_active() {
self.dec_save_cursor();
self.screen.activate_alt_screen();
self.screen.activate_alt_screen(self.seqno);
self.set_cursor_pos(&Position::Absolute(0), &Position::Absolute(0));
self.pen = CellAttributes::default();
self.erase_in_display(EraseInDisplay::EraseDisplay);
@ -1506,7 +1522,7 @@ impl TerminalState {
DecPrivateModeCode::ClearAndEnableAlternateScreen,
)) => {
if self.screen.is_alt_screen_active() {
self.screen.activate_primary_screen();
self.screen.activate_primary_screen(self.seqno);
self.dec_restore_cursor();
}
}
@ -1658,6 +1674,7 @@ impl TerminalState {
}
fn erase_in_display(&mut self, erase: EraseInDisplay) {
let seqno = self.seqno;
let cy = self.cursor.y;
let pen = self.pen.clone_sgr_only();
let rows = self.screen().physical_rows as VisibleRowIndex;
@ -1681,12 +1698,13 @@ impl TerminalState {
{
let screen = self.screen_mut();
for y in row_range.clone() {
screen.clear_line(y, col_range.clone(), &pen);
screen.clear_line(y, col_range.clone(), &pen, seqno);
}
}
}
fn perform_csi_edit(&mut self, edit: Edit) {
let seqno = self.seqno;
match edit {
Edit::DeleteCharacter(n) => {
let y = self.cursor.y;
@ -1698,7 +1716,7 @@ impl TerminalState {
let screen = self.screen_mut();
for _ in x..limit as usize {
screen.erase_cell(x, y, right_margin);
screen.erase_cell(x, y, right_margin, seqno);
}
}
}
@ -1712,6 +1730,7 @@ impl TerminalState {
&top_and_bottom_margins,
&left_and_right_margins,
n as usize,
seqno,
);
}
}
@ -1723,7 +1742,7 @@ impl TerminalState {
let blank = Cell::new(' ', self.pen.clone_sgr_only());
let screen = self.screen_mut();
for x in x..limit as usize {
screen.set_cell(x, y, &blank);
screen.set_cell(x, y, &blank, seqno);
}
}
}
@ -1739,7 +1758,7 @@ impl TerminalState {
EraseInLine::EraseLine => 0..cols,
};
self.screen_mut().clear_line(cy, range.clone(), &pen);
self.screen_mut().clear_line(cy, range.clone(), &pen, seqno);
}
Edit::InsertCharacter(n) => {
// https://vt100.net/docs/vt510-rm/ICH.html
@ -1756,7 +1775,7 @@ impl TerminalState {
let margin = self.left_and_right_margins.end;
let screen = self.screen_mut();
for _ in 0..n as usize {
screen.insert_cell(x, y, margin);
screen.insert_cell(x, y, margin, seqno);
}
}
}
@ -1770,6 +1789,7 @@ impl TerminalState {
&top_and_bottom_margins,
&left_and_right_margins,
n as usize,
seqno,
);
}
}
@ -1814,7 +1834,7 @@ impl TerminalState {
let line_idx = screen.phys_row(y);
let line = screen.line_mut(line_idx);
line.set_cell(x, cell.clone());
line.set_cell(x, cell.clone(), seqno);
}
x += 1;
if x > left_and_right_margins.end - 1 {
@ -1866,6 +1886,7 @@ impl TerminalState {
}
fn perform_csi_cursor(&mut self, cursor: Cursor) {
let seqno = self.seqno;
match cursor {
Cursor::SetTopAndBottomMargins { top, bottom } => {
let rows = self.screen().physical_rows;
@ -1938,7 +1959,7 @@ impl TerminalState {
self.cursor.x = new_x;
self.wrap_next = false;
let screen = self.screen_mut();
screen.dirty_line(y);
screen.dirty_line(y, seqno);
}
Cursor::Right(n) => {
@ -1956,7 +1977,7 @@ impl TerminalState {
self.cursor.x = new_x;
self.wrap_next = false;
let screen = self.screen_mut();
screen.dirty_line(y);
screen.dirty_line(y, seqno);
}
Cursor::Up(n) => {
@ -1982,8 +2003,8 @@ impl TerminalState {
self.cursor.y = new_y;
self.wrap_next = false;
let screen = self.screen_mut();
screen.dirty_line(old_y);
screen.dirty_line(new_y);
screen.dirty_line(old_y, seqno);
screen.dirty_line(new_y, seqno);
}
Cursor::Down(n) => {
// https://vt100.net/docs/vt510-rm/CUD.html
@ -2001,8 +2022,8 @@ impl TerminalState {
self.cursor.y = new_y;
self.wrap_next = false;
let screen = self.screen_mut();
screen.dirty_line(old_y);
screen.dirty_line(new_y);
screen.dirty_line(old_y, seqno);
screen.dirty_line(new_y, seqno);
}
Cursor::CharacterAndLinePosition { line, col } | Cursor::Position { line, col } => self
@ -2026,7 +2047,7 @@ impl TerminalState {
self.wrap_next = false;
let y = self.cursor.y;
let screen = self.screen_mut();
screen.dirty_line(y);
screen.dirty_line(y, seqno);
}
Cursor::CharacterPositionBackward(col) => self.set_cursor_pos(
@ -2064,8 +2085,8 @@ impl TerminalState {
self.cursor.x = self.left_and_right_margins.start;
self.wrap_next = false;
let screen = self.screen_mut();
screen.dirty_line(old_y);
screen.dirty_line(new_y);
screen.dirty_line(old_y, seqno);
screen.dirty_line(new_y, seqno);
}
Cursor::PrecedingLine(n) => {
// https://vt100.net/docs/vt510-rm/CPL.html
@ -2090,8 +2111,8 @@ impl TerminalState {
self.cursor.x = self.left_and_right_margins.start;
self.wrap_next = false;
let screen = self.screen_mut();
screen.dirty_line(old_y);
screen.dirty_line(new_y);
screen.dirty_line(old_y, seqno);
screen.dirty_line(new_y, seqno);
}
Cursor::ActivePositionReport { .. } => {

View File

@ -49,6 +49,8 @@ impl<'a> Performer<'a> {
}
fn flush_print(&mut self) {
let seqno = self.seqno;
let p = match self.print.take() {
Some(s) => s,
None => return,
@ -142,7 +144,7 @@ impl<'a> Performer<'a> {
let margin = self.left_and_right_margins.end;
let screen = self.screen_mut();
for _ in x..x + print_width as usize {
screen.insert_cell(x, y, margin);
screen.insert_cell(x, y, margin, seqno);
}
}
@ -156,7 +158,7 @@ impl<'a> Performer<'a> {
width,
cell
);
self.screen_mut().set_cell(x, y, &cell);
self.screen_mut().set_cell(x, y, &cell, seqno);
if !wrappable {
self.cursor.x += print_width;
@ -258,6 +260,7 @@ impl<'a> Performer<'a> {
}
fn control(&mut self, control: ControlCode) {
let seqno = self.seqno;
self.flush_print();
match control {
ControlCode::LineFeed | ControlCode::VerticalTab | ControlCode::FormFeed => {
@ -272,8 +275,8 @@ impl<'a> Performer<'a> {
} else {
(old_y + 1).min(self.screen().physical_rows as i64 - 1)
};
self.screen_mut().dirty_line(old_y);
self.screen_mut().dirty_line(y);
self.screen_mut().dirty_line(old_y, seqno);
self.screen_mut().dirty_line(y, seqno);
self.cursor.y = y;
self.wrap_next = false;
}
@ -289,7 +292,7 @@ impl<'a> Performer<'a> {
}
let y = self.cursor.y;
self.wrap_next = false;
self.screen_mut().dirty_line(y);
self.screen_mut().dirty_line(y, seqno);
}
ControlCode::Backspace => {
@ -379,6 +382,7 @@ impl<'a> Performer<'a> {
}
fn esc_dispatch(&mut self, esc: Esc) {
let seqno = self.seqno;
self.flush_print();
match esc {
Esc::Code(EscCode::StringTerminator) => {
@ -420,19 +424,19 @@ impl<'a> Performer<'a> {
Esc::Code(EscCode::DecDoubleHeightTopHalfLine) => {
let idx = self.screen.phys_row(self.cursor.y);
self.screen.line_mut(idx).set_double_height_top();
self.screen.line_mut(idx).set_double_height_top(seqno);
}
Esc::Code(EscCode::DecDoubleHeightBottomHalfLine) => {
let idx = self.screen.phys_row(self.cursor.y);
self.screen.line_mut(idx).set_double_height_bottom();
self.screen.line_mut(idx).set_double_height_bottom(seqno);
}
Esc::Code(EscCode::DecDoubleWidthLine) => {
let idx = self.screen.phys_row(self.cursor.y);
self.screen.line_mut(idx).set_double_width();
self.screen.line_mut(idx).set_double_width(seqno);
}
Esc::Code(EscCode::DecSingleWidthLine) => {
let idx = self.screen.phys_row(self.cursor.y);
self.screen.line_mut(idx).set_single_width();
self.screen.line_mut(idx).set_single_width(seqno);
}
Esc::Code(EscCode::DecScreenAlignmentDisplay) => {
@ -445,10 +449,11 @@ impl<'a> Performer<'a> {
for y in 0..screen.physical_rows as VisibleRowIndex {
let line_idx = screen.phys_row(y);
let line = screen.line_mut(line_idx);
line.resize(col_range.end);
line.resize(col_range.end, seqno);
line.fill_range(
col_range.clone(),
&Cell::new('E', CellAttributes::default()),
seqno,
);
}
@ -462,6 +467,7 @@ impl<'a> Performer<'a> {
// reset graphic rendition, erase all positions, move active position to first
// character position of first line.
Esc::Code(EscCode::FullReset) => {
let seqno = self.seqno;
self.pen = Default::default();
self.cursor = Default::default();
self.wrap_next = false;
@ -493,7 +499,7 @@ impl<'a> Performer<'a> {
self.top_and_bottom_margins = 0..self.screen().physical_rows as VisibleRowIndex;
self.left_and_right_margins = 0..self.screen().physical_cols;
self.screen.activate_primary_screen();
self.screen.activate_primary_screen(seqno);
self.erase_in_display(EraseInDisplay::EraseScrollback);
self.erase_in_display(EraseInDisplay::EraseDisplay);
if let Some(handler) = self.alert_handler.as_mut() {

View File

@ -169,7 +169,7 @@ fn test_ed() {
.set_background(color::AnsiColor::Navy)
.clone();
let mut line: Line = " ".into();
line.fill_range(0..3, &Cell::new(' ', attr.clone()));
line.fill_range(0..3, &Cell::new(' ', attr.clone()), SEQ_ZERO);
assert_lines_equal(
file!(),
line!(),

View File

@ -13,7 +13,7 @@ use std::cell::RefCell;
use std::sync::Arc;
use termwiz::escape::csi::{Edit, EraseInDisplay, EraseInLine};
use termwiz::escape::{OneBased, OperatingSystemCommand, CSI};
use termwiz::surface::{CursorShape, CursorVisibility};
use termwiz::surface::{CursorShape, CursorVisibility, SequenceNo, SEQ_ZERO};
#[derive(Debug)]
struct LocalClip {
@ -164,13 +164,19 @@ impl TestTerm {
);
}
fn assert_dirty_lines(&self, expected: &[usize], reason: Option<&str>) {
fn assert_dirty_lines(&self, seqno: SequenceNo, expected: &[usize], reason: Option<&str>) {
let dirty_indices: Vec<usize> = self
.screen()
.lines
.iter()
.enumerate()
.filter_map(|(i, line)| if line.is_dirty() { Some(i) } else { None })
.filter_map(|(i, line)| {
if line.changed_since(seqno) {
Some(i)
} else {
None
}
})
.collect();
assert_eq!(
&dirty_indices, &expected,
@ -220,15 +226,6 @@ fn assert_lines_equal(
None => break,
};
if compare.contains(Compare::DIRTY) {
assert_eq!(
line.is_dirty(),
expect.is_dirty(),
"line {} dirty didn't match",
idx,
);
}
if compare.contains(Compare::ATTRS) {
let line_attrs: Vec<_> = line.cells().iter().map(|c| c.attrs().clone()).collect();
let expect_attrs: Vec<_> = expect.cells().iter().map(|c| c.attrs().clone()).collect();
@ -503,21 +500,27 @@ fn basic_output() {
fn cursor_movement_damage() {
let mut term = TestTerm::new(2, 3, 0);
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_dirty_lines(&[0, 1], None);
term.assert_dirty_lines(seqno, &[0, 1], None);
term.cup(0, 1);
term.clean_dirty_lines();
let seqno = term.current_seqno();
term.print("\x08");
term.assert_cursor_pos(0, 1, Some("BS doesn't change the line"));
// Since we didn't move, the line isn't dirty
term.assert_dirty_lines(&[], None);
term.clean_dirty_lines();
term.assert_dirty_lines(seqno, &[], None);
let seqno = term.current_seqno();
term.cup(0, 0);
term.assert_dirty_lines(&[0, 1], Some("cursor movement dirties old and new lines"));
term.assert_dirty_lines(
seqno,
&[0, 1],
Some("cursor movement dirties old and new lines"),
);
}
const NUM_COLS: usize = 3;
@ -625,6 +628,7 @@ fn scroll_down_within_left_and_right_margins() {
fn test_delete_lines() {
let mut term = TestTerm::new(5, 3, 0);
let seqno = term.current_seqno();
term.print("111\r\n222\r\n333\r\n444\r\n555");
assert_visible_contents(
&term,
@ -632,20 +636,20 @@ fn test_delete_lines() {
line!(),
&["111", "222", "333", "444", "555"],
);
term.assert_dirty_lines(&[0, 1, 2, 3, 4], None);
term.assert_dirty_lines(seqno, &[0, 1, 2, 3, 4], None);
term.cup(0, 1);
term.clean_dirty_lines();
term.assert_dirty_lines(&[], None);
let seqno = term.current_seqno();
term.assert_dirty_lines(seqno, &[], None);
term.delete_lines(2);
assert_visible_contents(&term, file!(), line!(), &["111", "444", "555", "", ""]);
term.assert_dirty_lines(&[1, 2, 3, 4], None);
term.clean_dirty_lines();
term.assert_dirty_lines(seqno, &[1, 2, 3, 4], None);
term.cup(0, 3);
term.print("aaa\r\nbbb");
term.cup(0, 1);
term.clean_dirty_lines();
let seqno = term.current_seqno();
assert_visible_contents(
&term,
file!(),
@ -660,16 +664,17 @@ fn test_delete_lines() {
term.delete_lines(2);
assert_visible_contents(&term, file!(), line!(), &["111", "aaa", "", "", "bbb"]);
term.assert_dirty_lines(&[0, 1, 2, 3], None);
term.assert_dirty_lines(seqno, &[0, 1, 2, 3], None);
// expand the scroll region to fill the screen
term.set_scroll_region(0, 4);
term.clean_dirty_lines();
let seqno = term.current_seqno();
print_all_lines(&term);
term.delete_lines(1);
assert_visible_contents(&term, file!(), line!(), &["aaa", "", "", "bbb", ""]);
term.assert_dirty_lines(&[4], None);
term.assert_dirty_lines(seqno, &[4], None);
}
/// Test DEC Special Graphics character set.
@ -971,6 +976,7 @@ fn test_hyperlinks() {
.set_hyperlink(Some(Arc::clone(&otherlink)))
.clone(),
),
SEQ_ZERO,
);
partial_line.set_cell(
1,
@ -980,6 +986,7 @@ fn test_hyperlinks() {
.set_hyperlink(Some(Arc::clone(&otherlink)))
.clone(),
),
SEQ_ZERO,
);
assert_lines_equal(

View File

@ -1,7 +1,7 @@
use crate::cell::{Cell, CellAttributes};
use crate::cellcluster::CellCluster;
use crate::hyperlink::Rule;
use crate::surface::Change;
use crate::surface::{Change, SequenceNo, SEQ_ZERO};
use bitflags::bitflags;
#[cfg(feature = "use_serde")]
use serde::{Deserialize, Serialize};
@ -13,9 +13,7 @@ bitflags! {
#[cfg_attr(feature="use_serde", derive(Serialize, Deserialize))]
struct LineBits : u8 {
const NONE = 0;
/// The contents of the Line have changed and cached or
/// derived data will need to be reassessed.
const DIRTY = 1;
const _UNUSED = 1;
/// The line contains 1+ cells with explicit hyperlinks set
const HAS_HYPERLINK = 1<<1;
/// true if we have scanned for implicit hyperlinks
@ -51,6 +49,7 @@ bitflags! {
#[derive(Debug, Clone, PartialEq)]
pub struct Line {
cells: Vec<Cell>,
seqno: SequenceNo,
bits: LineBits,
}
@ -64,8 +63,12 @@ impl Line {
pub fn with_width(width: usize) -> Self {
let mut cells = Vec::with_capacity(width);
cells.resize(width, Cell::default());
let bits = LineBits::DIRTY;
Self { bits, cells }
let bits = LineBits::NONE;
Self {
bits,
cells,
seqno: SEQ_ZERO,
}
}
pub fn from_text(s: &str, attrs: &CellAttributes) -> Line {
@ -82,7 +85,8 @@ impl Line {
Line {
cells,
bits: LineBits::DIRTY,
bits: LineBits::NONE,
seqno: SEQ_ZERO,
}
}
@ -94,22 +98,23 @@ impl Line {
line
}
pub fn resize_and_clear(&mut self, width: usize) {
pub fn resize_and_clear(&mut self, width: usize, seqno: SequenceNo) {
let blank = Cell::default();
self.cells.clear();
self.cells.resize(width, blank);
self.cells.shrink_to_fit();
self.bits = LineBits::DIRTY;
self.update_last_change_seqno(seqno);
self.bits = LineBits::NONE;
}
pub fn resize(&mut self, width: usize) {
pub fn resize(&mut self, width: usize, seqno: SequenceNo) {
self.cells.resize(width, Cell::default());
self.bits |= LineBits::DIRTY;
self.update_last_change_seqno(seqno);
}
/// Wrap the line so that it fits within the provided width.
/// Returns the list of resultant line(s)
pub fn wrap(mut self, width: usize) -> Vec<Self> {
pub fn wrap(mut self, width: usize, seqno: SequenceNo) -> Vec<Self> {
if let Some(end_idx) = self.cells.iter().rposition(|c| c.str() != " ") {
self.cells.resize(end_idx + 1, Cell::default());
@ -119,18 +124,19 @@ impl Line {
.map(|chunk| {
let mut line = Line {
cells: chunk.to_vec(),
bits: LineBits::DIRTY,
bits: LineBits::NONE,
seqno: seqno,
};
if line.cells.len() == width {
// Ensure that we don't forget that we wrapped
line.set_last_cell_was_wrapped(true);
line.set_last_cell_was_wrapped(true, seqno);
}
line
})
.collect();
// The last of the chunks wasn't actually wrapped
if let Some(line) = lines.last_mut() {
line.set_last_cell_was_wrapped(false);
line.set_last_cell_was_wrapped(false, seqno);
}
lines
} else {
@ -138,25 +144,22 @@ impl Line {
}
}
/// Check whether the dirty bit is set.
/// If it is set, then something about the line has changed since
/// the dirty bit was last cleared.
#[inline]
pub fn is_dirty(&self) -> bool {
(self.bits & LineBits::DIRTY) == LineBits::DIRTY
/// Returns true if the line's last changed seqno is more recent
/// than the provided seqno parameter
pub fn changed_since(&self, seqno: SequenceNo) -> bool {
self.seqno == SEQ_ZERO || self.seqno > seqno
}
/// Force the dirty bit set.
/// FIXME: this is abused by term::Screen, want to remove or rethink it.
#[inline]
pub fn set_dirty(&mut self) {
self.bits |= LineBits::DIRTY;
pub fn current_seqno(&self) -> SequenceNo {
self.seqno
}
/// Clear the dirty bit.
/// Annotate the line with the sequence number of a change.
/// This can be used together with Line::changed_since to
/// manage caching and rendering
#[inline]
pub fn clear_dirty(&mut self) {
self.bits &= !LineBits::DIRTY;
pub fn update_last_change_seqno(&mut self, seqno: SequenceNo) {
self.seqno = self.seqno.max(seqno);
}
/// Check whether the reverse video bit is set. If it is set,
@ -169,9 +172,9 @@ impl Line {
/// Force the reverse bit set. This also implicitly sets dirty.
#[inline]
pub fn set_reverse(&mut self, reverse: bool) {
pub fn set_reverse(&mut self, reverse: bool, seqno: SequenceNo) {
self.bits.set(LineBits::REVERSE, reverse);
self.bits.insert(LineBits::DIRTY);
self.update_last_change_seqno(seqno);
}
/// Check whether the line is single-width.
@ -187,9 +190,9 @@ impl Line {
/// Force single-width. This also implicitly sets
/// double-height-(top/bottom) and dirty.
#[inline]
pub fn set_single_width(&mut self) {
pub fn set_single_width(&mut self, seqno: SequenceNo) {
self.bits.remove(LineBits::DOUBLE_WIDTH_HEIGHT_MASK);
self.bits.insert(LineBits::DIRTY);
self.update_last_change_seqno(seqno);
}
/// Check whether the line is double-width and not double-height.
@ -201,10 +204,11 @@ impl Line {
/// Force double-width. This also implicitly sets
/// double-height-(top/bottom) and dirty.
#[inline]
pub fn set_double_width(&mut self) {
pub fn set_double_width(&mut self, seqno: SequenceNo) {
self.bits
.remove(LineBits::DOUBLE_HEIGHT_TOP | LineBits::DOUBLE_HEIGHT_BOTTOM);
self.bits.insert(LineBits::DOUBLE_WIDTH | LineBits::DIRTY);
self.bits.insert(LineBits::DOUBLE_WIDTH);
self.update_last_change_seqno(seqno);
}
/// Check whether the line is double-height-top.
@ -217,10 +221,11 @@ impl Line {
/// Force double-height top-half. This also implicitly sets
/// double-width and dirty.
#[inline]
pub fn set_double_height_top(&mut self) {
pub fn set_double_height_top(&mut self, seqno: SequenceNo) {
self.bits.remove(LineBits::DOUBLE_HEIGHT_BOTTOM);
self.bits
.insert(LineBits::DOUBLE_WIDTH | LineBits::DOUBLE_HEIGHT_TOP | LineBits::DIRTY);
.insert(LineBits::DOUBLE_WIDTH | LineBits::DOUBLE_HEIGHT_TOP);
self.update_last_change_seqno(seqno);
}
/// Check whether the line is double-height-bottom.
@ -233,15 +238,16 @@ impl Line {
/// Force double-height bottom-half. This also implicitly sets
/// double-width and dirty.
#[inline]
pub fn set_double_height_bottom(&mut self) {
pub fn set_double_height_bottom(&mut self, seqno: SequenceNo) {
self.bits.remove(LineBits::DOUBLE_HEIGHT_TOP);
self.bits
.insert(LineBits::DOUBLE_WIDTH | LineBits::DOUBLE_HEIGHT_BOTTOM | LineBits::DIRTY);
.insert(LineBits::DOUBLE_WIDTH | LineBits::DOUBLE_HEIGHT_BOTTOM);
self.update_last_change_seqno(seqno);
}
/// If we have any cells with an implicit hyperlink, remove the hyperlink
/// from the cell attributes but leave the remainder of the attributes alone.
pub fn invalidate_implicit_hyperlinks(&mut self) {
pub fn invalidate_implicit_hyperlinks(&mut self, seqno: SequenceNo) {
if (self.bits & (LineBits::SCANNED_IMPLICIT_HYPERLINKS | LineBits::HAS_IMPLICIT_HYPERLINKS))
== LineBits::NONE
{
@ -267,7 +273,7 @@ impl Line {
}
self.bits &= !LineBits::HAS_IMPLICIT_HYPERLINKS;
self.bits |= LineBits::DIRTY;
self.update_last_change_seqno(seqno);
}
/// Scan through the line and look for sequences that match the provided
@ -342,6 +348,7 @@ impl Line {
Self {
bits: self.bits,
cells,
seqno: SEQ_ZERO,
}
}
@ -404,12 +411,17 @@ impl Line {
/// of cells to avoid partial rendering concerns.
/// Similarly, when we assign a cell, we need to blank out those
/// occluded successor cells.
pub fn set_cell(&mut self, idx: usize, cell: Cell) -> &Cell {
self.set_cell_impl(idx, cell, false)
pub fn set_cell(&mut self, idx: usize, cell: Cell, seqno: SequenceNo) -> &Cell {
self.set_cell_impl(idx, cell, false, seqno)
}
pub fn set_cell_clearing_image_placements(&mut self, idx: usize, cell: Cell) -> &Cell {
self.set_cell_impl(idx, cell, true)
pub fn set_cell_clearing_image_placements(
&mut self,
idx: usize,
cell: Cell,
seqno: SequenceNo,
) -> &Cell {
self.set_cell_impl(idx, cell, true, seqno)
}
fn raw_set_cell(&mut self, idx: usize, mut cell: Cell, clear: bool) {
@ -425,7 +437,7 @@ impl Line {
self.cells[idx] = cell;
}
fn set_cell_impl(&mut self, idx: usize, cell: Cell, clear: bool) -> &Cell {
fn set_cell_impl(&mut self, idx: usize, cell: Cell, clear: bool, seqno: SequenceNo) -> &Cell {
let width = cell.width();
// if the line isn't wide enough, pad it out with the default attributes.
@ -439,8 +451,8 @@ impl Line {
self.cells.resize(idx + width.max(1), Cell::default());
}
self.invalidate_implicit_hyperlinks();
self.bits |= LineBits::DIRTY;
self.invalidate_implicit_hyperlinks(seqno);
self.update_last_change_seqno(seqno);
if cell.attrs().hyperlink().is_some() {
self.bits |= LineBits::HAS_HYPERLINK;
}
@ -463,11 +475,12 @@ impl Line {
mut start_idx: usize,
text: &str,
attr: CellAttributes,
seqno: SequenceNo,
) {
for (i, c) in text.graphemes(true).enumerate() {
let cell = Cell::new_grapheme(c, attr.clone());
let width = cell.width();
self.set_cell(i + start_idx, cell);
self.set_cell(i + start_idx, cell, seqno);
// Compensate for required spacing/placement of
// double width characters
@ -490,8 +503,8 @@ impl Line {
}
}
pub fn insert_cell(&mut self, x: usize, cell: Cell, right_margin: usize) {
self.invalidate_implicit_hyperlinks();
pub fn insert_cell(&mut self, x: usize, cell: Cell, right_margin: usize, seqno: SequenceNo) {
self.invalidate_implicit_hyperlinks(seqno);
if right_margin <= self.cells.len() {
self.cells.remove(right_margin - 1);
@ -509,23 +522,23 @@ impl Line {
}
self.cells.insert(x, cell);
self.set_dirty();
self.update_last_change_seqno(seqno);
}
pub fn erase_cell(&mut self, x: usize) {
pub fn erase_cell(&mut self, x: usize, seqno: SequenceNo) {
if x >= self.cells.len() {
// Already implicitly erased
return;
}
self.invalidate_implicit_hyperlinks();
self.invalidate_implicit_hyperlinks(seqno);
self.invalidate_grapheme_at_or_before(x);
self.cells.remove(x);
self.cells.push(Cell::default());
self.set_dirty();
self.update_last_change_seqno(seqno);
}
pub fn erase_cell_with_margin(&mut self, x: usize, right_margin: usize) {
self.invalidate_implicit_hyperlinks();
pub fn erase_cell_with_margin(&mut self, x: usize, right_margin: usize, seqno: SequenceNo) {
self.invalidate_implicit_hyperlinks(seqno);
if x < self.cells.len() {
self.invalidate_grapheme_at_or_before(x);
self.cells.remove(x);
@ -533,10 +546,10 @@ impl Line {
if right_margin <= self.cells.len() {
self.cells.insert(right_margin - 1, Cell::default());
}
self.set_dirty();
self.update_last_change_seqno(seqno);
}
pub fn prune_trailing_blanks(&mut self) {
pub fn prune_trailing_blanks(&mut self, seqno: SequenceNo) {
let def_attr = CellAttributes::default();
if let Some(end_idx) = self
.cells
@ -544,15 +557,16 @@ impl Line {
.rposition(|c| c.str() != " " || c.attrs() != &def_attr)
{
self.cells.resize(end_idx + 1, Cell::default());
self.update_last_change_seqno(seqno);
}
}
pub fn fill_range(&mut self, cols: Range<usize>, cell: &Cell) {
pub fn fill_range(&mut self, cols: Range<usize>, cell: &Cell, seqno: SequenceNo) {
for x in cols {
// FIXME: we can skip the look-back for second and subsequent iterations
self.set_cell_impl(x, cell.clone(), true);
self.set_cell_impl(x, cell.clone(), true, seqno);
}
self.prune_trailing_blanks();
self.prune_trailing_blanks(seqno);
}
/// Iterates the visible cells, respecting the width of the cell.
@ -604,10 +618,10 @@ impl Line {
/// Adjust the value of the wrapped attribute on the last cell of this
/// line.
pub fn set_last_cell_was_wrapped(&mut self, wrapped: bool) {
pub fn set_last_cell_was_wrapped(&mut self, wrapped: bool, seqno: SequenceNo) {
if let Some(cell) = self.cells.last_mut() {
cell.attrs_mut().set_wrapped(wrapped);
self.set_dirty();
self.update_last_change_seqno(seqno);
}
}
@ -615,9 +629,9 @@ impl Line {
/// to this line.
/// This function is used by rewrapping logic when joining wrapped
/// lines back together.
pub fn append_line(&mut self, mut other: Line) {
pub fn append_line(&mut self, mut other: Line, seqno: SequenceNo) {
self.cells.append(&mut other.cells);
self.set_dirty();
self.update_last_change_seqno(seqno);
}
/// mutable access the cell data, but the caller must take care

View File

@ -73,6 +73,7 @@ impl CursorShape {
/// SequenceNo indicates a logical position within a stream of changes.
/// The sequence is only meaningful within a given `Surface` instance.
pub type SequenceNo = usize;
pub const SEQ_ZERO: SequenceNo = 0;
/// The `Surface` type represents the contents of a terminal screen.
/// It is not directly connected to a terminal device.
@ -229,7 +230,7 @@ impl Surface {
pub fn resize(&mut self, width: usize, height: usize) {
self.lines.resize(height, Line::with_width(width));
for line in &mut self.lines {
line.resize(width);
line.resize(width, self.seqno);
}
self.width = width;
self.height = height;
@ -339,6 +340,7 @@ impl Surface {
)))
.clone(),
),
self.seqno,
);
xpos += xsize;
@ -353,7 +355,7 @@ impl Surface {
self.attributes = CellAttributes::default().set_background(color).clone();
let cleared = Cell::new(' ', self.attributes.clone());
for line in &mut self.lines {
line.fill_range(0..self.width, &cleared);
line.fill_range(0..self.width, &cleared, self.seqno);
}
self.xpos = 0;
self.ypos = 0;
@ -362,16 +364,16 @@ impl Surface {
fn clear_eos(&mut self, color: ColorAttribute) {
self.attributes = CellAttributes::default().set_background(color).clone();
let cleared = Cell::new(' ', self.attributes.clone());
self.lines[self.ypos].fill_range(self.xpos..self.width, &cleared);
self.lines[self.ypos].fill_range(self.xpos..self.width, &cleared, self.seqno);
for line in &mut self.lines.iter_mut().skip(self.ypos + 1) {
line.fill_range(0..self.width, &cleared);
line.fill_range(0..self.width, &cleared, self.seqno);
}
}
fn clear_eol(&mut self, color: ColorAttribute) {
self.attributes = CellAttributes::default().set_background(color).clone();
let cleared = Cell::new(' ', self.attributes.clone());
self.lines[self.ypos].fill_range(self.xpos..self.width, &cleared);
self.lines[self.ypos].fill_range(self.xpos..self.width, &cleared, self.seqno);
}
fn scroll_screen_up(&mut self) {
@ -447,7 +449,7 @@ impl Surface {
// the model, which seems like a lossy design choice.
let width = cell.width().max(1);
self.lines[self.ypos].set_cell(self.xpos, cell);
self.lines[self.ypos].set_cell(self.xpos, cell, self.seqno);
// Increment the position now; we'll defer processing
// wrapping until the next printed character, otherwise

View File

@ -20,6 +20,7 @@ use std::ops::Range;
use std::rc::Rc;
use std::sync::Arc;
use termwiz::input::KeyEvent;
use termwiz::surface::SequenceNo;
use url::Url;
use wezterm_term::color::ColorPalette;
use wezterm_term::{Alert, Clipboard, KeyCode, KeyModifiers, Line, MouseEvent, StableRowIndex};
@ -173,8 +174,16 @@ impl Pane for ClientPane {
self.renderable.borrow().get_lines(lines)
}
fn get_dirty_lines(&self, lines: Range<StableRowIndex>) -> RangeSet<StableRowIndex> {
self.renderable.borrow().get_dirty_lines(lines)
fn get_current_seqno(&self) -> SequenceNo {
self.renderable.borrow().get_current_seqno()
}
fn get_changed_since(
&self,
lines: Range<StableRowIndex>,
seqno: SequenceNo,
) -> RangeSet<StableRowIndex> {
self.renderable.borrow().get_changed_since(lines, seqno)
}
fn set_clipboard(&self, clipboard: &Arc<dyn Clipboard>) {

View File

@ -17,6 +17,7 @@ use std::sync::Arc;
use std::time::{Duration, Instant};
use termwiz::cell::{Cell, CellAttributes, Underline};
use termwiz::color::AnsiColor;
use termwiz::surface::{SequenceNo, SEQ_ZERO};
use url::Url;
use wezterm_term::{KeyCode, KeyModifiers};
use wezterm_term::{Line, StableRowIndex};
@ -68,6 +69,7 @@ pub struct RenderableInner {
lines: LruCache<StableRowIndex, LineEntry>,
pub title: String,
pub working_dir: Option<Url>,
pub seqno: SequenceNo,
fetch_limiter: RateLimiter,
@ -113,6 +115,7 @@ impl RenderableInner {
last_late_dirty: now,
last_input_rtt: 0,
input_serial: InputSerial::empty(),
seqno: SEQ_ZERO,
}
}
@ -174,11 +177,11 @@ impl RenderableInner {
self.cursor_position.x = self.cursor_position.x.saturating_sub(1);
}
KeyCode::Delete => {
line.erase_cell(self.cursor_position.x);
line.erase_cell(self.cursor_position.x, SEQ_ZERO);
}
KeyCode::Backspace => {
if self.cursor_position.x > 0 {
line.erase_cell(self.cursor_position.x - 1);
line.erase_cell(self.cursor_position.x - 1, SEQ_ZERO);
self.cursor_position.x -= 1;
}
}
@ -190,7 +193,7 @@ impl RenderableInner {
.clone(),
);
let cell = line.set_cell(self.cursor_position.x, cell);
let cell = line.set_cell(self.cursor_position.x, cell, SEQ_ZERO);
// Adjust the cursor to reflect the width of this new cell
self.cursor_position.x += cell.width();
}
@ -251,13 +254,13 @@ impl RenderableInner {
if row == 0 {
for cell in text_line.cells() {
line.set_cell(self.cursor_position.x, cell.clone());
line.set_cell(self.cursor_position.x, cell.clone(), SEQ_ZERO);
self.cursor_position.x += cell.width();
}
} else {
// The pasted line replaces the data for the existing line
line.resize_and_clear(0);
line.append_line(text_line);
line.resize_and_clear(0, SEQ_ZERO);
line.append_line(text_line, SEQ_ZERO);
self.cursor_position.x = line.cells().len();
}
}
@ -340,6 +343,7 @@ impl RenderableInner {
self.dimensions = delta.dimensions;
self.title = delta.title;
self.working_dir = delta.working_dir.map(Into::into);
self.seqno = delta.seqno;
let config = configuration();
for (stable_row, line) in delta.bonus_lines.lines() {
@ -664,7 +668,7 @@ impl RenderableState {
result
.last_mut()
.unwrap()
.overlay_text_with_attribute(col, &status, attr);
.overlay_text_with_attribute(col, &status, attr, SEQ_ZERO);
}
}
@ -675,7 +679,15 @@ impl RenderableState {
(lines.start, result)
}
pub fn get_dirty_lines(&self, lines: Range<StableRowIndex>) -> RangeSet<StableRowIndex> {
pub fn get_current_seqno(&self) -> SequenceNo {
self.inner.borrow().seqno
}
pub fn get_changed_since(
&self,
lines: Range<StableRowIndex>,
_seqno: SequenceNo,
) -> RangeSet<StableRowIndex> {
let mut inner = self.inner.borrow_mut();
if let Err(err) = inner.poll() {
// We allow for BrokenPromise here for now; for a TLS backed
@ -710,7 +722,7 @@ impl RenderableState {
}
if !result.is_empty() {
log::trace!("get_dirty_lines: {:?}", result);
log::trace!("get_changed_since: {:?}", result);
}
result

View File

@ -10,6 +10,7 @@ use std::cell::{RefCell, RefMut};
use std::ops::Range;
use std::rc::Rc;
use std::sync::Arc;
use termwiz::surface::SequenceNo;
use unicode_segmentation::*;
use url::Url;
use wezterm_term::color::ColorPalette;
@ -520,8 +521,16 @@ impl Pane for CopyOverlay {
self.render.borrow_mut().cursor
}
fn get_dirty_lines(&self, lines: Range<StableRowIndex>) -> RangeSet<StableRowIndex> {
self.delegate.get_dirty_lines(lines)
fn get_current_seqno(&self) -> SequenceNo {
self.delegate.get_current_seqno()
}
fn get_changed_since(
&self,
lines: Range<StableRowIndex>,
seqno: SequenceNo,
) -> RangeSet<StableRowIndex> {
self.delegate.get_changed_since(lines, seqno)
}
fn get_lines(&self, lines: Range<StableRowIndex>) -> (StableRowIndex, Vec<Line>) {

View File

@ -14,6 +14,7 @@ use std::rc::Rc;
use std::sync::Arc;
use termwiz::cell::{Cell, CellAttributes};
use termwiz::color::AnsiColor;
use termwiz::surface::{SequenceNo, SEQ_ZERO};
use url::Url;
use wezterm_term::color::ColorPalette;
use wezterm_term::{Clipboard, KeyCode, KeyModifiers, Line, MouseEvent, StableRowIndex};
@ -406,8 +407,16 @@ impl Pane for QuickSelectOverlay {
}
}
fn get_dirty_lines(&self, lines: Range<StableRowIndex>) -> RangeSet<StableRowIndex> {
let mut dirty = self.delegate.get_dirty_lines(lines.clone());
fn get_current_seqno(&self) -> SequenceNo {
self.delegate.get_current_seqno()
}
fn get_changed_since(
&self,
lines: Range<StableRowIndex>,
seqno: SequenceNo,
) -> RangeSet<StableRowIndex> {
let mut dirty = self.delegate.get_changed_since(lines.clone(), seqno);
dirty.add_set(&self.renderer.borrow().dirty_results);
dirty.intersection_with_range(lines)
}
@ -429,7 +438,7 @@ impl Pane for QuickSelectOverlay {
if stable_idx == search_row {
// Replace with search UI
let rev = CellAttributes::default().set_reverse(true).clone();
line.fill_range(0..dims.cols, &Cell::new(' ', rev.clone()));
line.fill_range(0..dims.cols, &Cell::new(' ', rev.clone()), SEQ_ZERO);
line.overlay_text_with_attribute(
0,
&format!(
@ -437,6 +446,7 @@ impl Pane for QuickSelectOverlay {
renderer.selection,
),
rev,
SEQ_ZERO,
);
renderer.last_bar_pos = Some(search_row);
} else if let Some(matches) = renderer.by_line.get(&stable_idx) {
@ -460,7 +470,7 @@ impl Pane for QuickSelectOverlay {
attr.set_background(AnsiColor::Black)
.set_foreground(AnsiColor::Olive)
.set_reverse(false);
line.set_cell(m.range.start + idx, Cell::new(c, attr));
line.set_cell(m.range.start + idx, Cell::new(c, attr), SEQ_ZERO);
}
}
}

View File

@ -13,6 +13,7 @@ use std::rc::Rc;
use std::sync::Arc;
use termwiz::cell::{Cell, CellAttributes};
use termwiz::color::AnsiColor;
use termwiz::surface::{SequenceNo, SEQ_ZERO};
use url::Url;
use wezterm_term::color::ColorPalette;
use wezterm_term::{Clipboard, KeyCode, KeyModifiers, Line, MouseEvent, StableRowIndex};
@ -275,8 +276,16 @@ impl Pane for SearchOverlay {
}
}
fn get_dirty_lines(&self, lines: Range<StableRowIndex>) -> RangeSet<StableRowIndex> {
let mut dirty = self.delegate.get_dirty_lines(lines.clone());
fn get_current_seqno(&self) -> SequenceNo {
self.delegate.get_current_seqno()
}
fn get_changed_since(
&self,
lines: Range<StableRowIndex>,
seqno: SequenceNo,
) -> RangeSet<StableRowIndex> {
let mut dirty = self.delegate.get_changed_since(lines.clone(), seqno);
dirty.add_set(&self.renderer.borrow().dirty_results);
dirty.intersection_with_range(lines)
}
@ -298,7 +307,7 @@ impl Pane for SearchOverlay {
if stable_idx == search_row {
// Replace with search UI
let rev = CellAttributes::default().set_reverse(true).clone();
line.fill_range(0..dims.cols, &Cell::new(' ', rev.clone()));
line.fill_range(0..dims.cols, &Cell::new(' ', rev.clone()), SEQ_ZERO);
let mode = &match renderer.pattern {
Pattern::CaseSensitiveString(_) => "case-sensitive",
Pattern::CaseInSensitiveString(_) => "ignore-case",
@ -314,6 +323,7 @@ impl Pane for SearchOverlay {
mode
),
rev,
SEQ_ZERO,
);
renderer.last_bar_pos = Some(search_row);
} else if let Some(matches) = renderer.by_line.get(&stable_idx) {

View File

@ -5,6 +5,7 @@ use mux::pane::Pane;
use std::cmp::Ordering;
use std::ops::Range;
use termwiz::surface::line::DoubleClickRange;
use termwiz::surface::SequenceNo;
use wezterm_term::{SemanticZone, StableRowIndex};
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
@ -14,6 +15,8 @@ pub struct Selection {
pub start: Option<SelectionCoordinate>,
/// Holds the not-normalized selection range.
pub range: Option<SelectionRange>,
/// When the selection was made wrt. the pane content
pub seqno: SequenceNo,
}
pub use config::keyassignment::SelectionMode;

View File

@ -8,6 +8,7 @@ use termwiz::color::ColorSpec;
use termwiz::escape::csi::Sgr;
use termwiz::escape::parser::Parser;
use termwiz::escape::{Action, ControlCode, CSI};
use termwiz::surface::SEQ_ZERO;
use wezterm_term::Line;
#[derive(Clone, Debug, PartialEq)]
@ -265,7 +266,7 @@ impl TabBarState {
if n + len > tab_width_max {
break;
}
line.set_cell(x, cell);
line.set_cell(x, cell, SEQ_ZERO);
x += len;
n += len;
}
@ -287,7 +288,7 @@ impl TabBarState {
for c in cells {
let len = c.width();
line.set_cell(x, c.clone());
line.set_cell(x, c.clone(), SEQ_ZERO);
x += len;
}
@ -306,7 +307,7 @@ impl TabBarState {
);
for idx in x..title_width {
line.set_cell(idx, black_cell.clone());
line.set_cell(idx, black_cell.clone(), SEQ_ZERO);
}
let rhs_cells = parse_status_text(right_status, black_cell.attrs().clone());
@ -314,7 +315,7 @@ impl TabBarState {
let skip = rhs_cells.len() - rhs_len;
for (idx, cell) in rhs_cells.into_iter().skip(skip).rev().enumerate() {
line.set_cell(title_width - (1 + idx), cell);
line.set_cell(title_width - (1 + idx), cell, SEQ_ZERO);
}
Self { line, items }

View File

@ -920,43 +920,42 @@ impl TermWindow {
}
}
fn check_for_dirty_lines_and_invalidate_selection(&mut self, pane: &Rc<dyn Pane>) -> bool {
fn check_for_dirty_lines_and_invalidate_selection(&mut self, pane: &Rc<dyn Pane>) {
let dims = pane.get_dimensions();
let viewport = self
.get_viewport(pane.pane_id())
.unwrap_or(dims.physical_top);
let visible_range = viewport..viewport + dims.viewport_rows as StableRowIndex;
let dirty = pane.get_dirty_lines(visible_range);
let seqno = self.selection(pane.pane_id()).seqno;
let dirty = pane.get_changed_since(visible_range, seqno);
if !dirty.is_empty() {
if pane.downcast_ref::<SearchOverlay>().is_none()
&& pane.downcast_ref::<CopyOverlay>().is_none()
&& pane.downcast_ref::<QuickSelectOverlay>().is_none()
{
// If any of the changed lines intersect with the
// selection, then we need to clear the selection, but not
// when the search overlay is active; the search overlay
// marks lines as dirty to force invalidate them for
// highlighting purpose but also manipulates the selection
// and we want to allow it to retain the selection it made!
if dirty.is_empty() {
return;
}
if pane.downcast_ref::<SearchOverlay>().is_none()
&& pane.downcast_ref::<CopyOverlay>().is_none()
&& pane.downcast_ref::<QuickSelectOverlay>().is_none()
{
// If any of the changed lines intersect with the
// selection, then we need to clear the selection, but not
// when the search overlay is active; the search overlay
// marks lines as dirty to force invalidate them for
// highlighting purpose but also manipulates the selection
// and we want to allow it to retain the selection it made!
let clear_selection =
if let Some(selection_range) = self.selection(pane.pane_id()).range.as_ref() {
let selection_rows = selection_range.rows();
selection_rows.into_iter().any(|row| dirty.contains(row))
} else {
false
};
let clear_selection =
if let Some(selection_range) = self.selection(pane.pane_id()).range.as_ref() {
let selection_rows = selection_range.rows();
selection_rows.into_iter().any(|row| dirty.contains(row))
} else {
false
};
if clear_selection {
self.selection(pane.pane_id()).range.take();
self.selection(pane.pane_id()).start.take();
}
if clear_selection {
self.selection(pane.pane_id()).range.take();
self.selection(pane.pane_id()).start.take();
self.selection(pane.pane_id()).seqno = pane.get_current_seqno();
}
true
} else {
false
}
}
}

View File

@ -48,6 +48,7 @@ impl super::TermWindow {
mode: Option<SelectionMode>,
pane: &Rc<dyn Pane>,
) {
self.selection(pane.pane_id()).seqno = pane.get_current_seqno();
let mode = mode.unwrap_or(SelectionMode::Cell);
let (x, y) = self.last_mouse_terminal_coords;
match mode {
@ -165,6 +166,7 @@ impl super::TermWindow {
}
}
self.selection(pane.pane_id()).seqno = pane.get_current_seqno();
self.window.as_ref().unwrap().invalidate();
}
}

View File

@ -24,6 +24,7 @@ rcgen = "0.8"
smol = "1.2"
url = "2"
wezterm-term = { path = "../term", features=["use_serde"] }
termwiz = { path = "../termwiz", features=["use_serde"] }
[target."cfg(windows)".dependencies]
uds_windows = "0.1"

View File

@ -9,11 +9,11 @@ use mux::Mux;
use percent_encoding::percent_decode_str;
use portable_pty::PtySize;
use promise::spawn::spawn_into_main_thread;
use rangeset::RangeSet;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use std::time::Instant;
use termwiz::surface::SequenceNo;
use url::Url;
use wezterm_term::terminal::{Alert, Clipboard, ClipboardSelection};
use wezterm_term::StableRowIndex;
@ -42,9 +42,9 @@ pub(crate) struct PerPane {
title: String,
working_dir: Option<Url>,
dimensions: RenderableDimensions,
dirty_lines: RangeSet<StableRowIndex>,
mouse_grabbed: bool,
sent_initial_palette: bool,
seqno: SequenceNo,
pub(crate) notifications: Vec<Alert>,
}
@ -80,10 +80,11 @@ impl PerPane {
changed = true;
}
let mut all_dirty_lines =
pane.get_dirty_lines(0..dims.physical_top + dims.viewport_rows as StableRowIndex);
let dirty_delta = all_dirty_lines.difference(&self.dirty_lines);
if !dirty_delta.is_empty() {
let mut all_dirty_lines = pane.get_changed_since(
0..dims.physical_top + dims.viewport_rows as StableRowIndex,
self.seqno,
);
if !all_dirty_lines.is_empty() {
changed = true;
}
@ -115,27 +116,23 @@ impl PerPane {
self.title = title.clone();
self.working_dir = working_dir.clone();
self.dimensions = dims;
self.dirty_lines = all_dirty_lines;
self.mouse_grabbed = mouse_grabbed;
self.seqno = pane.get_current_seqno();
let dirty_lines = dirty_delta.iter().cloned().collect();
let bonus_lines = bonus_lines.into();
Some(GetPaneRenderChangesResponse {
pane_id: pane.pane_id(),
mouse_grabbed,
dirty_lines,
dirty_lines: all_dirty_lines.iter().cloned().collect(),
dimensions: dims,
cursor_position,
title,
bonus_lines,
working_dir: working_dir.map(Into::into),
input_serial: force_with_input_serial,
seqno: self.seqno,
})
}
fn mark_clean(&mut self, stable_row: StableRowIndex) {
self.dirty_lines.remove(stable_row);
}
}
fn maybe_push_pane_changes(
@ -509,7 +506,6 @@ impl SessionHandler {
}
Pdu::GetLines(GetLines { pane_id, lines }) => {
let per_pane = self.per_pane(pane_id);
spawn_into_main_thread(async move {
catch(
move || {
@ -518,13 +514,11 @@ impl SessionHandler {
.get_pane(pane_id)
.ok_or_else(|| anyhow!("no such pane {}", pane_id))?;
let mut lines_and_indices = vec![];
let mut per_pane = per_pane.lock().unwrap();
for range in lines {
let (first_row, lines) = pane.get_lines(range);
for (idx, line) in lines.into_iter().enumerate() {
let stable_row = first_row + idx as StableRowIndex;
per_pane.mark_clean(stable_row);
lines_and_indices.push((stable_row, line));
}
}