mirror of
https://github.com/wezterm/wezterm.git
synced 2026-08-03 08:44:56 +08:00
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:
parent
2e158f58f3
commit
eae327efcc
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -5088,6 +5088,7 @@ dependencies = [
|
||||
"rangeset",
|
||||
"rcgen",
|
||||
"smol",
|
||||
"termwiz",
|
||||
"uds_windows",
|
||||
"url",
|
||||
"wezterm-term",
|
||||
|
||||
@ -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)]
|
||||
|
||||
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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!();
|
||||
}
|
||||
|
||||
|
||||
@ -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>) {
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 { .. } => {
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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!(),
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>) {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 }
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user