fix(performance): output buffer (#567)

* work

* work

* fix(performance): output buffer

* style(import): remove extraneous

* style(fmt): make rustfmt happy

* fix(performance): minor adjustments to padding and truncating

* style(fmt): make rustfmt happy

* style(clippy): make clippy happy
This commit is contained in:
Aram Drevekenin 2021-06-03 14:03:05 +02:00 committed by GitHub
parent 4b7fe3ca7b
commit 0bab7c1245
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 244 additions and 71 deletions

View file

@ -95,10 +95,9 @@ fn transfer_rows_down(
next_lines.push(next_line); next_lines.push(next_line);
next_lines.append(&mut top_non_canonical_rows_in_dst); next_lines.append(&mut top_non_canonical_rows_in_dst);
next_lines = match max_dst_width { next_lines = match max_dst_width {
Some(max_row_width) => { Some(max_row_width) => Row::from_rows(next_lines, max_row_width)
Row::from_rows(next_lines).split_to_rows_of_length(max_row_width) .split_to_rows_of_length(max_row_width),
} None => vec![Row::from_rows(next_lines, 0)],
None => vec![Row::from_rows(next_lines)],
}; };
if next_lines.is_empty() { if next_lines.is_empty() {
// no more lines at source, the line we popped was probably empty // no more lines at source, the line we popped was probably empty
@ -114,11 +113,12 @@ fn transfer_rows_down(
if !next_lines.is_empty() { if !next_lines.is_empty() {
match max_src_width { match max_src_width {
Some(max_row_width) => { Some(max_row_width) => {
let excess_rows = Row::from_rows(next_lines).split_to_rows_of_length(max_row_width); let excess_rows = Row::from_rows(next_lines, max_row_width)
.split_to_rows_of_length(max_row_width);
source.extend(excess_rows); source.extend(excess_rows);
} }
None => { None => {
let excess_row = Row::from_rows(next_lines); let excess_row = Row::from_rows(next_lines, 0);
bounded_push(source, excess_row); bounded_push(source, excess_row);
} }
} }
@ -144,10 +144,9 @@ fn transfer_rows_up(
} }
next_lines.push(next_line); next_lines.push(next_line);
next_lines = match max_dst_width { next_lines = match max_dst_width {
Some(max_row_width) => { Some(max_row_width) => Row::from_rows(next_lines, max_row_width)
Row::from_rows(next_lines).split_to_rows_of_length(max_row_width) .split_to_rows_of_length(max_row_width),
} None => vec![Row::from_rows(next_lines, 0)],
None => vec![Row::from_rows(next_lines)],
}; };
} else { } else {
break; // no more rows break; // no more rows
@ -158,13 +157,14 @@ fn transfer_rows_up(
if !next_lines.is_empty() { if !next_lines.is_empty() {
match max_src_width { match max_src_width {
Some(max_row_width) => { Some(max_row_width) => {
let excess_rows = Row::from_rows(next_lines).split_to_rows_of_length(max_row_width); let excess_rows = Row::from_rows(next_lines, max_row_width)
.split_to_rows_of_length(max_row_width);
for row in excess_rows { for row in excess_rows {
source.insert(0, row); source.insert(0, row);
} }
} }
None => { None => {
let excess_row = Row::from_rows(next_lines); let excess_row = Row::from_rows(next_lines, 0);
source.insert(0, excess_row); source.insert(0, excess_row);
} }
} }
@ -191,6 +191,107 @@ pub fn create_horizontal_tabstops(columns: usize) -> BTreeSet<usize> {
horizontal_tabstops horizontal_tabstops
} }
#[derive(Debug)]
pub struct CharacterChunk {
pub terminal_characters: Vec<TerminalCharacter>,
pub x: usize,
pub y: usize,
}
#[derive(Clone, Debug)]
pub struct OutputBuffer {
changed_lines: Vec<usize>, // line index
should_update_all_lines: bool,
}
impl Default for OutputBuffer {
fn default() -> Self {
OutputBuffer {
changed_lines: vec![],
should_update_all_lines: true, // first time we should do a full render
}
}
}
impl OutputBuffer {
pub fn update_line(&mut self, line_index: usize) {
if !self.should_update_all_lines {
self.changed_lines.push(line_index);
}
}
pub fn update_all_lines(&mut self) {
self.clear();
self.should_update_all_lines = true;
}
pub fn clear(&mut self) {
self.changed_lines.clear();
self.should_update_all_lines = false;
}
pub fn changed_chunks_in_viewport(
&self,
viewport: &[Row],
viewport_width: usize,
viewport_height: usize,
) -> Vec<CharacterChunk> {
if self.should_update_all_lines {
let mut changed_chunks = Vec::with_capacity(viewport.len());
for line_index in 0..viewport_height {
let terminal_characters =
self.extract_line_from_viewport(line_index, viewport, viewport_width);
changed_chunks.push(CharacterChunk {
x: 0,
y: line_index,
terminal_characters,
});
}
changed_chunks
} else {
let mut line_changes = self.changed_lines.to_vec();
line_changes.sort_unstable();
line_changes.dedup();
let mut changed_chunks = Vec::with_capacity(line_changes.len());
for line_index in line_changes {
let terminal_characters =
self.extract_line_from_viewport(line_index, viewport, viewport_width);
changed_chunks.push(CharacterChunk {
x: 0,
y: line_index,
terminal_characters,
});
}
changed_chunks
}
}
fn extract_characters_from_row(
&self,
row: &Row,
viewport_width: usize,
) -> Vec<TerminalCharacter> {
let mut terminal_characters: Vec<TerminalCharacter> = row.columns.iter().copied().collect();
// pad row
let row_width = row.width();
if row_width < viewport_width {
let mut padding = vec![EMPTY_TERMINAL_CHARACTER; viewport_width - row_width];
terminal_characters.append(&mut padding);
}
terminal_characters
}
fn extract_line_from_viewport(
&self,
line_index: usize,
viewport: &[Row],
viewport_width: usize,
) -> Vec<TerminalCharacter> {
match viewport.get(line_index) {
// TODO: iterator?
Some(row) => self.extract_characters_from_row(row, viewport_width),
None => {
vec![EMPTY_TERMINAL_CHARACTER; viewport_width]
}
}
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct Grid { pub struct Grid {
lines_above: VecDeque<Row>, lines_above: VecDeque<Row>,
@ -204,6 +305,7 @@ pub struct Grid {
active_charset: CharsetIndex, active_charset: CharsetIndex,
preceding_char: Option<TerminalCharacter>, preceding_char: Option<TerminalCharacter>,
colors: Palette, colors: Palette,
output_buffer: OutputBuffer,
pub should_render: bool, pub should_render: bool,
pub cursor_key_mode: bool, // DECCKM - when set, cursor keys should send ANSI direction codes (eg. "OD") instead of the arrow keys (eg. "") pub cursor_key_mode: bool, // DECCKM - when set, cursor keys should send ANSI direction codes (eg. "OD") instead of the arrow keys (eg. "")
pub erasure_mode: bool, // ERM pub erasure_mode: bool, // ERM
@ -232,7 +334,7 @@ impl Grid {
pub fn new(rows: usize, columns: usize, colors: Palette) -> Self { pub fn new(rows: usize, columns: usize, colors: Palette) -> Self {
Grid { Grid {
lines_above: VecDeque::with_capacity(SCROLL_BACK), lines_above: VecDeque::with_capacity(SCROLL_BACK),
viewport: vec![Row::new().canonical()], viewport: vec![Row::new(columns).canonical()],
lines_below: vec![], lines_below: vec![],
horizontal_tabstops: create_horizontal_tabstops(columns), horizontal_tabstops: create_horizontal_tabstops(columns),
cursor: Cursor::new(0, 0), cursor: Cursor::new(0, 0),
@ -251,8 +353,12 @@ impl Grid {
active_charset: Default::default(), active_charset: Default::default(),
pending_messages_to_pty: vec![], pending_messages_to_pty: vec![],
colors, colors,
output_buffer: Default::default(),
} }
} }
pub fn render_full_viewport(&mut self) {
self.output_buffer.update_all_lines();
}
pub fn advance_to_next_tabstop(&mut self, styles: CharacterStyles) { pub fn advance_to_next_tabstop(&mut self, styles: CharacterStyles) {
let mut next_tabstop = None; let mut next_tabstop = None;
for tabstop in self.horizontal_tabstops.iter() { for tabstop in self.horizontal_tabstops.iter() {
@ -272,6 +378,7 @@ impl Grid {
let mut empty_character = EMPTY_TERMINAL_CHARACTER; let mut empty_character = EMPTY_TERMINAL_CHARACTER;
empty_character.styles = styles; empty_character.styles = styles;
self.pad_current_line_until(self.cursor.x); self.pad_current_line_until(self.cursor.x);
self.output_buffer.update_line(self.cursor.y);
} }
pub fn move_to_previous_tabstop(&mut self) { pub fn move_to_previous_tabstop(&mut self) {
let mut previous_tabstop = None; let mut previous_tabstop = None;
@ -367,6 +474,7 @@ impl Grid {
let line_to_insert_at_viewport_top = self.lines_above.pop_back().unwrap(); let line_to_insert_at_viewport_top = self.lines_above.pop_back().unwrap();
self.viewport.insert(0, line_to_insert_at_viewport_top); self.viewport.insert(0, line_to_insert_at_viewport_top);
} }
self.output_buffer.update_all_lines();
} }
pub fn scroll_down_one_line(&mut self) { pub fn scroll_down_one_line(&mut self) {
if !self.lines_below.is_empty() && self.viewport.len() == self.height { if !self.lines_below.is_empty() && self.viewport.len() == self.height {
@ -380,6 +488,7 @@ impl Grid {
} }
let line_to_insert_at_viewport_bottom = self.lines_below.remove(0); let line_to_insert_at_viewport_bottom = self.lines_below.remove(0);
self.viewport.push(line_to_insert_at_viewport_bottom); self.viewport.push(line_to_insert_at_viewport_bottom);
self.output_buffer.update_all_lines();
} }
} }
pub fn change_size(&mut self, new_rows: usize, new_columns: usize) { pub fn change_size(&mut self, new_rows: usize, new_columns: usize) {
@ -417,7 +526,7 @@ impl Grid {
for mut canonical_line in viewport_canonical_lines { for mut canonical_line in viewport_canonical_lines {
let mut canonical_line_parts: Vec<Row> = vec![]; let mut canonical_line_parts: Vec<Row> = vec![];
if canonical_line.columns.is_empty() { if canonical_line.columns.is_empty() {
canonical_line_parts.push(Row::new().canonical()); canonical_line_parts.push(Row::new(new_columns).canonical());
} }
while !canonical_line.columns.is_empty() { while !canonical_line.columns.is_empty() {
let next_wrap = if canonical_line.width() > new_columns { let next_wrap = if canonical_line.width() > new_columns {
@ -515,8 +624,12 @@ impl Grid {
if self.scroll_region.is_some() { if self.scroll_region.is_some() {
self.set_scroll_region_to_viewport_size(); self.set_scroll_region_to_viewport_size();
} }
self.output_buffer.update_all_lines();
} }
pub fn as_character_lines(&self) -> Vec<Vec<TerminalCharacter>> { pub fn as_character_lines(&self) -> Vec<Vec<TerminalCharacter>> {
// this is only used in the tests
// it's not part of testing the app, but rather is used to interpret the snapshots created
// by it
let mut lines: Vec<Vec<TerminalCharacter>> = self let mut lines: Vec<Vec<TerminalCharacter>> = self
.viewport .viewport
.iter() .iter()
@ -537,6 +650,13 @@ impl Grid {
} }
lines lines
} }
pub fn read_changes(&mut self) -> Vec<CharacterChunk> {
let changes =
self.output_buffer
.changed_chunks_in_viewport(&self.viewport, self.width, self.height);
self.output_buffer.clear();
changes
}
pub fn cursor_coordinates(&self) -> Option<(usize, usize)> { pub fn cursor_coordinates(&self) -> Option<(usize, usize)> {
if self.cursor.is_hidden { if self.cursor.is_hidden {
None None
@ -548,17 +668,22 @@ impl Grid {
for _ in 0..count { for _ in 0..count {
self.scroll_up_one_line(); self.scroll_up_one_line();
} }
self.output_buffer.update_all_lines();
} }
pub fn move_viewport_down(&mut self, count: usize) { pub fn move_viewport_down(&mut self, count: usize) {
for _ in 0..count { for _ in 0..count {
self.scroll_down_one_line(); self.scroll_down_one_line();
} }
self.output_buffer.update_all_lines();
} }
pub fn reset_viewport(&mut self) { pub fn reset_viewport(&mut self) {
let row_count_below = self.lines_below.len(); let row_count_below = self.lines_below.len();
for _ in 0..row_count_below { for _ in 0..row_count_below {
self.scroll_down_one_line(); self.scroll_down_one_line();
} }
if row_count_below > 0 {
self.output_buffer.update_all_lines();
}
} }
pub fn rotate_scroll_region_up(&mut self, count: usize) { pub fn rotate_scroll_region_up(&mut self, count: usize) {
if let Some((scroll_region_top, scroll_region_bottom)) = self.scroll_region { if let Some((scroll_region_top, scroll_region_bottom)) = self.scroll_region {
@ -572,6 +697,7 @@ impl Grid {
.insert(scroll_region_top, Row::from_columns(columns).canonical()); .insert(scroll_region_top, Row::from_columns(columns).canonical());
} }
} }
self.output_buffer.update_all_lines(); // TODO: only update scroll region lines
} }
} }
pub fn rotate_scroll_region_down(&mut self, count: usize) { pub fn rotate_scroll_region_down(&mut self, count: usize) {
@ -586,6 +712,7 @@ impl Grid {
self.viewport.push(Row::from_columns(columns).canonical()); self.viewport.push(Row::from_columns(columns).canonical());
} }
} }
self.output_buffer.update_all_lines(); // TODO: only update scroll region lines
} }
} }
pub fn fill_viewport(&mut self, character: TerminalCharacter) { pub fn fill_viewport(&mut self, character: TerminalCharacter) {
@ -594,6 +721,7 @@ impl Grid {
let columns = vec![character; self.width]; let columns = vec![character; self.width];
self.viewport.push(Row::from_columns(columns).canonical()); self.viewport.push(Row::from_columns(columns).canonical());
} }
self.output_buffer.update_all_lines();
} }
pub fn add_canonical_line(&mut self) { pub fn add_canonical_line(&mut self) {
if let Some((scroll_region_top, scroll_region_bottom)) = self.scroll_region { if let Some((scroll_region_top, scroll_region_bottom)) = self.scroll_region {
@ -616,6 +744,7 @@ impl Grid {
} else { } else {
self.viewport.push(Row::from_columns(columns).canonical()); self.viewport.push(Row::from_columns(columns).canonical());
} }
self.output_buffer.update_all_lines(); // TODO: only update scroll region lines
return; return;
} }
} }
@ -623,7 +752,7 @@ impl Grid {
// FIXME: this should add an empty line with the pad_character // FIXME: this should add an empty line with the pad_character
// but for some reason this breaks rendering in various situations // but for some reason this breaks rendering in various situations
// it needs to be investigated and fixed // it needs to be investigated and fixed
let new_row = Row::new().canonical(); let new_row = Row::new(self.width).canonical();
self.viewport.push(new_row); self.viewport.push(new_row);
} }
if self.cursor.y == self.height - 1 { if self.cursor.y == self.height - 1 {
@ -635,8 +764,10 @@ impl Grid {
Some(self.width), Some(self.width),
None, None,
); );
self.output_buffer.update_all_lines();
} else { } else {
self.cursor.y += 1; self.cursor.y += 1;
self.output_buffer.update_line(self.cursor.y);
} }
} }
pub fn move_cursor_to_beginning_of_line(&mut self) { pub fn move_cursor_to_beginning_of_line(&mut self) {
@ -646,25 +777,27 @@ impl Grid {
match self.viewport.get_mut(self.cursor.y) { match self.viewport.get_mut(self.cursor.y) {
Some(row) => { Some(row) => {
row.insert_character_at(terminal_character, self.cursor.x); row.insert_character_at(terminal_character, self.cursor.x);
if row.len() > self.width { // if row.len() > self.width {
if row.width() > self.width {
row.truncate(self.width); row.truncate(self.width);
} }
self.output_buffer.update_line(self.cursor.y);
} }
None => { None => {
// pad lines until cursor if they do not exist // pad lines until cursor if they do not exist
for _ in self.viewport.len()..self.cursor.y { for _ in self.viewport.len()..self.cursor.y {
self.viewport.push(Row::new().canonical()); self.viewport.push(Row::new(self.width).canonical());
} }
self.viewport self.viewport.push(
.push(Row::new().with_character(terminal_character).canonical()); Row::new(self.width)
.with_character(terminal_character)
.canonical(),
);
self.output_buffer.update_all_lines();
} }
} }
} }
pub fn add_character_at_cursor_position( pub fn add_character_at_cursor_position(&mut self, terminal_character: TerminalCharacter) {
&mut self,
terminal_character: TerminalCharacter,
max_width: usize,
) {
match self.viewport.get_mut(self.cursor.y) { match self.viewport.get_mut(self.cursor.y) {
Some(row) => { Some(row) => {
if self.insert_mode { if self.insert_mode {
@ -672,15 +805,19 @@ impl Grid {
} else { } else {
row.add_character_at(terminal_character, self.cursor.x); row.add_character_at(terminal_character, self.cursor.x);
} }
row.truncate(max_width); self.output_buffer.update_line(self.cursor.y);
} }
None => { None => {
// pad lines until cursor if they do not exist // pad lines until cursor if they do not exist
for _ in self.viewport.len()..self.cursor.y { for _ in self.viewport.len()..self.cursor.y {
self.viewport.push(Row::new().canonical()); self.viewport.push(Row::new(self.width).canonical());
} }
self.viewport self.viewport.push(
.push(Row::new().with_character(terminal_character).canonical()); Row::new(self.width)
.with_character(terminal_character)
.canonical(),
);
self.output_buffer.update_line(self.cursor.y);
} }
} }
} }
@ -702,17 +839,19 @@ impl Grid {
Some(self.width), Some(self.width),
None, None,
); );
let wrapped_row = Row::new(); let wrapped_row = Row::new(self.width);
self.viewport.push(wrapped_row); self.viewport.push(wrapped_row);
self.output_buffer.update_all_lines();
} else { } else {
self.cursor.y += 1; self.cursor.y += 1;
if self.viewport.len() <= self.cursor.y { if self.viewport.len() <= self.cursor.y {
let line_wrapped_row = Row::new(); let line_wrapped_row = Row::new(self.width);
self.viewport.push(line_wrapped_row); self.viewport.push(line_wrapped_row);
self.output_buffer.update_line(self.cursor.y);
} }
} }
} }
self.add_character_at_cursor_position(terminal_character, self.width); self.add_character_at_cursor_position(terminal_character);
self.move_cursor_forward_until_edge(character_width); self.move_cursor_forward_until_edge(character_width);
} }
pub fn move_cursor_forward_until_edge(&mut self, count: usize) { pub fn move_cursor_forward_until_edge(&mut self, count: usize) {
@ -724,10 +863,12 @@ impl Grid {
.get_mut(self.cursor.y) .get_mut(self.cursor.y)
.unwrap() .unwrap()
.replace_and_pad_end(self.cursor.x, self.width, replace_with); .replace_and_pad_end(self.cursor.x, self.width, replace_with);
self.output_buffer.update_line(self.cursor.y);
} }
pub fn replace_characters_in_line_before_cursor(&mut self, replace_with: TerminalCharacter) { pub fn replace_characters_in_line_before_cursor(&mut self, replace_with: TerminalCharacter) {
let row = self.viewport.get_mut(self.cursor.y).unwrap(); let row = self.viewport.get_mut(self.cursor.y).unwrap();
row.replace_and_pad_beginning(self.cursor.x, replace_with); row.replace_and_pad_beginning(self.cursor.x, replace_with);
self.output_buffer.update_line(self.cursor.y);
} }
pub fn clear_all_after_cursor(&mut self, replace_with: TerminalCharacter) { pub fn clear_all_after_cursor(&mut self, replace_with: TerminalCharacter) {
if let Some(cursor_row) = self.viewport.get_mut(self.cursor.y) { if let Some(cursor_row) = self.viewport.get_mut(self.cursor.y) {
@ -737,6 +878,7 @@ impl Grid {
for row in self.viewport.iter_mut().skip(self.cursor.y + 1) { for row in self.viewport.iter_mut().skip(self.cursor.y + 1) {
row.replace_columns(replace_with_columns.clone()); row.replace_columns(replace_with_columns.clone());
} }
self.output_buffer.update_all_lines(); // TODO: only update the changed lines
} }
} }
pub fn clear_all_before_cursor(&mut self, replace_with: TerminalCharacter) { pub fn clear_all_before_cursor(&mut self, replace_with: TerminalCharacter) {
@ -746,10 +888,12 @@ impl Grid {
for row in self.viewport.iter_mut().take(self.cursor.y) { for row in self.viewport.iter_mut().take(self.cursor.y) {
row.replace_columns(replace_with_columns.clone()); row.replace_columns(replace_with_columns.clone());
} }
self.output_buffer.update_all_lines(); // TODO: only update the changed lines
} }
} }
pub fn clear_cursor_line(&mut self) { pub fn clear_cursor_line(&mut self) {
self.viewport.get_mut(self.cursor.y).unwrap().truncate(0); self.viewport.get_mut(self.cursor.y).unwrap().truncate(0);
self.output_buffer.update_line(self.cursor.y);
} }
pub fn clear_all(&mut self, replace_with: TerminalCharacter) { pub fn clear_all(&mut self, replace_with: TerminalCharacter) {
let replace_with_columns = vec![replace_with; self.width]; let replace_with_columns = vec![replace_with; self.width];
@ -757,17 +901,20 @@ impl Grid {
for row in self.viewport.iter_mut() { for row in self.viewport.iter_mut() {
row.replace_columns(replace_with_columns.clone()); row.replace_columns(replace_with_columns.clone());
} }
self.output_buffer.update_all_lines();
} }
fn pad_current_line_until(&mut self, position: usize) { fn pad_current_line_until(&mut self, position: usize) {
let current_row = self.viewport.get_mut(self.cursor.y).unwrap(); let current_row = self.viewport.get_mut(self.cursor.y).unwrap();
for _ in current_row.len()..position { for _ in current_row.len()..position {
current_row.push(EMPTY_TERMINAL_CHARACTER); current_row.push(EMPTY_TERMINAL_CHARACTER);
} }
self.output_buffer.update_line(self.cursor.y);
} }
fn pad_lines_until(&mut self, position: usize, pad_character: TerminalCharacter) { fn pad_lines_until(&mut self, position: usize, pad_character: TerminalCharacter) {
for _ in self.viewport.len()..=position { for _ in self.viewport.len()..=position {
let columns = vec![pad_character; self.width]; let columns = vec![pad_character; self.width];
self.viewport.push(Row::from_columns(columns).canonical()); self.viewport.push(Row::from_columns(columns).canonical());
self.output_buffer.update_line(self.viewport.len() - 1);
} }
} }
pub fn move_cursor_to(&mut self, x: usize, y: usize, pad_character: TerminalCharacter) { pub fn move_cursor_to(&mut self, x: usize, y: usize, pad_character: TerminalCharacter) {
@ -816,13 +963,15 @@ impl Grid {
if scroll_region_bottom < self.viewport.len() { if scroll_region_bottom < self.viewport.len() {
self.viewport.remove(scroll_region_bottom); self.viewport.remove(scroll_region_bottom);
} }
self.viewport.insert(current_line_index, Row::new()); // TODO: .canonical() ? self.viewport
.insert(current_line_index, Row::new(self.width)); // TODO: .canonical() ?
} else if current_line_index > scroll_region_top } else if current_line_index > scroll_region_top
&& current_line_index <= scroll_region_bottom && current_line_index <= scroll_region_bottom
{ {
self.move_cursor_up(count); self.move_cursor_up(count);
} }
} }
self.output_buffer.update_all_lines();
} }
pub fn move_cursor_down(&mut self, count: usize, pad_character: TerminalCharacter) { pub fn move_cursor_down(&mut self, count: usize, pad_character: TerminalCharacter) {
if let Some((scroll_region_top, scroll_region_bottom)) = self.scroll_region { if let Some((scroll_region_top, scroll_region_bottom)) = self.scroll_region {
@ -896,6 +1045,7 @@ impl Grid {
self.viewport.push(Row::from_columns(columns).canonical()); self.viewport.push(Row::from_columns(columns).canonical());
} }
} }
self.output_buffer.update_all_lines(); // TODO: move accurately
} }
} }
} }
@ -920,6 +1070,7 @@ impl Grid {
self.viewport self.viewport
.insert(current_line_index, Row::from_columns(columns).canonical()); .insert(current_line_index, Row::from_columns(columns).canonical());
} }
self.output_buffer.update_all_lines(); // TODO: move accurately
} }
} }
} }
@ -941,6 +1092,7 @@ impl Grid {
for i in 0..count { for i in 0..count {
current_row.replace_character_at(empty_character, self.cursor.x + i); current_row.replace_character_at(empty_character, self.cursor.x + i);
} }
self.output_buffer.update_line(self.cursor.y);
} }
pub fn erase_characters(&mut self, count: usize, empty_char_style: CharacterStyles) { pub fn erase_characters(&mut self, count: usize, empty_char_style: CharacterStyles) {
let mut empty_character = EMPTY_TERMINAL_CHARACTER; let mut empty_character = EMPTY_TERMINAL_CHARACTER;
@ -956,6 +1108,7 @@ impl Grid {
current_row.insert_character_at(empty_character, self.cursor.x); current_row.insert_character_at(empty_character, self.cursor.x);
} }
} }
self.output_buffer.update_line(self.cursor.y);
} }
fn add_newline(&mut self) { fn add_newline(&mut self) {
self.add_canonical_line(); self.add_canonical_line();
@ -967,7 +1120,7 @@ impl Grid {
fn reset_terminal_state(&mut self) { fn reset_terminal_state(&mut self) {
self.lines_above = VecDeque::with_capacity(SCROLL_BACK); self.lines_above = VecDeque::with_capacity(SCROLL_BACK);
self.lines_below = vec![]; self.lines_below = vec![];
self.viewport = vec![Row::new().canonical()]; self.viewport = vec![Row::new(self.width).canonical()];
self.alternative_lines_above_viewport_and_cursor = None; self.alternative_lines_above_viewport_and_cursor = None;
self.cursor_key_mode = false; self.cursor_key_mode = false;
self.scroll_region = None; self.scroll_region = None;
@ -978,6 +1131,8 @@ impl Grid {
self.erasure_mode = false; self.erasure_mode = false;
self.disable_linewrap = false; self.disable_linewrap = false;
self.cursor.change_shape(CursorShape::Block); self.cursor.change_shape(CursorShape::Block);
//debug_log_to_file(format!("u20"));
self.output_buffer.update_all_lines();
} }
fn set_preceding_character(&mut self, terminal_character: TerminalCharacter) { fn set_preceding_character(&mut self, terminal_character: TerminalCharacter) {
self.preceding_char = Some(terminal_character); self.preceding_char = Some(terminal_character);
@ -987,6 +1142,8 @@ impl Grid {
impl Perform for Grid { impl Perform for Grid {
fn print(&mut self, c: char) { fn print(&mut self, c: char) {
let c = self.cursor.charsets[self.active_charset].map(c); let c = self.cursor.charsets[self.active_charset].map(c);
// TODO: CONTINUE HERE - the slowness is coming from this function, do some debugging to
// see if it can be mitigated somehow?
// apparently, building TerminalCharacter like this without a "new" method // apparently, building TerminalCharacter like this without a "new" method
// is a little faster // is a little faster
let terminal_character = TerminalCharacter { let terminal_character = TerminalCharacter {
@ -1306,8 +1463,10 @@ impl Perform for Grid {
&mut self.lines_above, &mut self.lines_above,
VecDeque::with_capacity(SCROLL_BACK), VecDeque::with_capacity(SCROLL_BACK),
); );
let current_viewport = let current_viewport = std::mem::replace(
std::mem::replace(&mut self.viewport, vec![Row::new().canonical()]); &mut self.viewport,
vec![Row::new(self.width).canonical()],
);
let current_cursor = std::mem::replace(&mut self.cursor, Cursor::new(0, 0)); let current_cursor = std::mem::replace(&mut self.cursor, Cursor::new(0, 0));
self.alternative_lines_above_viewport_and_cursor = self.alternative_lines_above_viewport_and_cursor =
Some((current_lines_above, current_viewport, current_cursor)); Some((current_lines_above, current_viewport, current_cursor));
@ -1594,28 +1753,22 @@ impl Debug for Row {
} }
} }
impl Default for Row { impl Row {
fn default() -> Self { pub fn new(width: usize) -> Self {
Row { Row {
columns: vec![], columns: Vec::with_capacity(width),
is_canonical: false, is_canonical: false,
} }
} }
}
impl Row {
pub fn new() -> Self {
Self::default()
}
pub fn from_columns(columns: Vec<TerminalCharacter>) -> Self { pub fn from_columns(columns: Vec<TerminalCharacter>) -> Self {
Row { Row {
columns, columns,
is_canonical: false, is_canonical: false,
} }
} }
pub fn from_rows(mut rows: Vec<Row>) -> Self { pub fn from_rows(mut rows: Vec<Row>, width: usize) -> Self {
if rows.is_empty() { if rows.is_empty() {
Row::new() Row::new(width)
} else { } else {
let mut first_row = rows.remove(0); let mut first_row = rows.remove(0);
for row in rows.iter_mut() { for row in rows.iter_mut() {
@ -1670,9 +1823,18 @@ impl Row {
} }
Ordering::Greater => { Ordering::Greater => {
let width_offset = self.excess_width_until(x); let width_offset = self.excess_width_until(x);
// this is much more performant than remove/insert let character_width = terminal_character.width;
self.columns.push(terminal_character); let replaced_character = std::mem::replace(
self.columns.swap_remove(x.saturating_sub(width_offset)); &mut self.columns[x.saturating_sub(width_offset)],
terminal_character,
);
if character_width > replaced_character.width {
// this is done in a verbose manner because of performance
let width_difference = character_width - replaced_character.width;
for _ in 0..width_difference {
self.columns.pop();
}
}
} }
} }
} }

View file

@ -129,6 +129,9 @@ impl Pane for TerminalPane {
fn set_should_render(&mut self, should_render: bool) { fn set_should_render(&mut self, should_render: bool) {
self.grid.should_render = should_render; self.grid.should_render = should_render;
} }
fn render_full_viewport(&mut self) {
self.grid.render_full_viewport();
}
fn selectable(&self) -> bool { fn selectable(&self) -> bool {
self.selectable self.selectable
} }
@ -153,8 +156,6 @@ impl Pane for TerminalPane {
fn render(&mut self) -> Option<String> { fn render(&mut self) -> Option<String> {
if self.should_render() { if self.should_render() {
let mut vte_output = String::new(); let mut vte_output = String::new();
let buffer_lines = &self.read_buffer_as_lines();
let display_cols = self.get_columns();
let mut character_styles = CharacterStyles::new(); let mut character_styles = CharacterStyles::new();
if self.grid.clear_viewport_before_rendering { if self.grid.clear_viewport_before_rendering {
for line_index in 0..self.grid.height { for line_index in 0..self.grid.height {
@ -171,26 +172,32 @@ impl Pane for TerminalPane {
} }
self.grid.clear_viewport_before_rendering = false; self.grid.clear_viewport_before_rendering = false;
} }
for (row, line) in buffer_lines.iter().enumerate() { let max_width = self.columns();
let x = self.get_x(); for character_chunk in self.grid.read_changes() {
let y = self.get_y(); let pane_x = self.get_x();
vte_output.push_str(&format!("\u{1b}[{};{}H\u{1b}[m", y + row + 1, x + 1)); // goto row/col and reset styles let pane_y = self.get_y();
for (col, t_character) in line.iter().enumerate() { let chunk_absolute_x = pane_x + character_chunk.x;
if col < display_cols { let chunk_absolute_y = pane_y + character_chunk.y;
// in some cases (eg. while resizing) some characters will spill over let terminal_characters = character_chunk.terminal_characters;
// before they are corrected by the shell (for the prompt) or by reflowing vte_output.push_str(&format!(
// lines "\u{1b}[{};{}H\u{1b}[m",
chunk_absolute_y + 1,
chunk_absolute_x + 1
)); // goto row/col and reset styles
let mut chunk_width = character_chunk.x;
for t_character in terminal_characters {
chunk_width += t_character.width;
if chunk_width > max_width {
break;
}
if let Some(new_styles) = if let Some(new_styles) =
character_styles.update_and_return_diff(&t_character.styles) character_styles.update_and_return_diff(&t_character.styles)
{ {
// the terminal keeps the previous styles as long as we're in the same
// line, so we only want to update the new styles here (this also
// includes resetting previous styles as needed)
vte_output.push_str(&new_styles.to_string()); vte_output.push_str(&new_styles.to_string());
} }
vte_output.push(t_character.character); vte_output.push(t_character.character);
} }
}
character_styles.clear(); character_styles.clear();
} }
self.set_should_render(false); self.set_should_render(false);

View file

@ -3,7 +3,7 @@ source: zellij-server/src/panes/./unit/grid_tests.rs
expression: "format!(\"{:?}\", grid)" expression: "format!(\"{:?}\", grid)"
--- ---
00 (C): A******************************************************************************BAAAAAAAAAAAAAAAAA 00 (C): A******************************************************************************BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
01 (C): 01 (C):
02 (C): 02 (C):
03 (C): Test of 'Insert Mode'. The top line should be 'A*** ... ***B'. Push <RETURN> 03 (C): Test of 'Insert Mode'. The top line should be 'A*** ... ***B'. Push <RETURN>

View file

@ -3,7 +3,7 @@ source: zellij-server/src/panes/./unit/grid_tests.rs
expression: "format!(\"{:?}\", grid)" expression: "format!(\"{:?}\", grid)"
--- ---
00 (C): ABAAAAAAAAAAAAAAAAA 00 (C): ABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
01 (C): 01 (C):
02 (C): 02 (C):
03 (C): Test of 'Delete Character'. The top line should be 'AB'. Push <RETURN> 03 (C): Test of 'Delete Character'. The top line should be 'AB'. Push <RETURN>

View file

@ -223,6 +223,7 @@ pub trait Pane {
// we should probably refactor away from this trait at some point // we should probably refactor away from this trait at some point
vec![] vec![]
} }
fn render_full_viewport(&mut self) {}
} }
impl Tab { impl Tab {
@ -619,6 +620,7 @@ impl Tab {
for message in messages_to_pty { for message in messages_to_pty {
self.write_to_pane_id(message, PaneId::Terminal(pid)); self.write_to_pane_id(message, PaneId::Terminal(pid));
} }
// self.render();
} }
} }
pub fn write_to_terminals_on_current_tab(&mut self, input_bytes: Vec<u8>) { pub fn write_to_terminals_on_current_tab(&mut self, input_bytes: Vec<u8>) {
@ -707,6 +709,7 @@ impl Tab {
active_terminal.rows() as u16, active_terminal.rows() as u16,
); );
} }
self.set_force_render();
self.render(); self.render();
self.toggle_fullscreen_is_active(); self.toggle_fullscreen_is_active();
} }
@ -717,6 +720,7 @@ impl Tab {
pub fn set_force_render(&mut self) { pub fn set_force_render(&mut self) {
for pane in self.panes.values_mut() { for pane in self.panes.values_mut() {
pane.set_should_render(true); pane.set_should_render(true);
pane.render_full_viewport();
} }
} }
pub fn is_sync_panes_active(&self) -> bool { pub fn is_sync_panes_active(&self) -> bool {