summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAidan Hobson Sayers <aidanhs@cantab.net>2024-01-22 14:28:57 +0000
committerGitHub <noreply@github.com>2024-01-22 15:28:57 +0100
commit8fd7a6789810f3a1de592c0190a5f91a1aaf8625 (patch)
tree7ca9ab643f65aef67db406d932ee670f9456551c
parent96b70e688eca7ce9a6f5496d150b53aeb6fcd378 (diff)
fix(terminal): improve reflow performance and general memory usage by reducing size of TerminalCharacter (#3043)
* refactor: Simplify transfer_rows_from_viewport_to_lines_above next_lines is always consolidated to a single Row, which immediately gets removed - we can remove some dead code as a result * perf: Batch remove rows from the viewport for performance Given a 1MB line catted into the terminal, a toggle-fullscreen + toggle-fullscreen + close-pane + `run true` goes from ~9s to ~3s * perf: Optimize Row::drain_until by splitting chars in one step Given a 10MB line catted into the terminal, a toggle-fullscreen + toggle-fullscreen + close-pane + `run true` goes from ~23s to ~20s * refactor: Simplify `if let` into a `.map` * refactor: There are only new saved coordinates when there were old ones * refactor: Unify viewport transfer: use common variable names * fix: Use same saved cursor logic in height resize as width See #2182 for original introduction that only added it in one branch, this fixes an issue where the saved cursor was incorrectly reset when the real cursor was * fix: Correct saved+real cursor calculations when reflowing long lines * fix: Don't create canonical lines if cursor ends on EOL after resize Previously if a 20 character line were split into two 10 character lines, the cursor would be placed on the line after the two lines. New characters would then be treated as a new canonical line. This commit fixes this by biasing cursors to the end of the previous line. * fix: for cursor index calculation in lines that are already wrapped * chore: test for real/saved cursor position being handled separately * chore: Apply cargo format * refactor: Unify viewport transfer: transfer + cursor update together * perf: Reduce size of TerminalCharacter from 72 to 60 bytes With a 10MB single line catted into a fresh terminal, VmRSS goes from 964092 kB to 874020 kB (as reported by /proc/<pid>/status) before/after this patch Given a 10MB line catted into the terminal, a toggle-fullscreen + toggle-fullscreen + close-pane + `run true` goes from ~15s to ~12.5s * fix(build): Don't unconditionally rebuild zellij-utils * refactor: Remove Copy impl on TerminalCharacter * perf: Rc styles to reduce TerminalCharacter from 60 to 24 bytes With a 10MB single line catted into a fresh terminal, VmRSS goes from 845156 kB to 478396 kB (as reported by /proc/<pid>/status) before/after this patch Given a 10MB line catted into the terminal, a toggle-fullscreen + toggle-fullscreen + close-pane + `run true` goes from ~12.5s to ~7s * perf: Remove RcCharacterStyles::Default, allow enum niche optimisation This reduces TerminalCharacter from 24 to 16 bytes With a 10MB single line catted into a fresh terminal, VmRSS goes from 478396 kB to 398108 kB (as reported by /proc/<pid>/status) before/after this patch Given a 10MB line catted into the terminal, a toggle-fullscreen + toggle-fullscreen + close-pane + `run true` goes from ~7s to ~4.75s * docs: link anchor omission from reset_all is deliberate reset_all is only used from ansi params, and ansi params don't control link anchor * fix: Remove no-op on variable that gets immediately dropped * refactor: Simplify replace_character_at logic The original condition checked absolute_x_index was in bounds, then used the index to manipulate it. This is equivalent to getting a ref to the character at that position and manipulating directly * chore: Run xtask format * chore(repo): update issue templates * Bump rust version to 1.75.0 (#3039) * rust-toolchain: Bump toolchain version to 1.69.0 which, compared to the previous 1.67.0, has the following impacts on `zellij`: - [Turn off debuginfo for build deps][2]: Increases build time (on my machine) from ~230 s in 1.67.0 to ~250 s now, *which is unexpected* This version also changes [handling of the `default-features` flag][3] when specifying dependencies in `Cargo.toml`. If a dependent crate requires `default-features = true` on a crate that is required as `default-features = false` further up the dependency tree, the `true` setting "wins". We only specify `default-features = false` for three crates total: - `names`: This is used only by us - `surf`: This is used only by us - `vte`: This is also required by `strip-ansi-escapes`, but that has `default-features = false` as well How this affects our transitive dependencies is unknown at this point. [2]: https://github.com/rust-lang/cargo/pull/11252/ [3]: https://github.com/rust-lang/cargo/pull/11409/ * rust-toolchain: Bump toolchain version to 1.70.0 which, compared to the previous 1.69.0, as the following impacts on `zellij`: 1. [Enable sparse registry checkout for crates.io by default][1] This drastically increases the time to first build on a fresh rust installation/a rust installation with a clean cargo registry cache. Previously it took about 75s to populate the deps/cache (with `cargo fetch --locked` and ~100 MBit/s network), whereas now the same process takes ~10 s. 2. [The `OnceCell` type is now part of std][2] In theory, this would allow us to cut a dependency from `zellij-utils`, but the `once_cell` crate is pulled in by another 16 deps, so there's no point in attempting it right now. Build times and binary sizes are unaffected by this change compared to the previous 1.69.0 toolchain. [1]: https://github.com/rust-lang/cargo/pull/11791/ [2]: https://doc.rust-lang.org/stable/std/cell/struct.OnceCell.html * rust-toolchain: Bump toolchain version to 1.75.0 which, compared to the previous 1.70.0, has the following impacts on `zellij`: 1. [cross-crate inlining][8] This should increase application performance, as functions can now be inlined across crates. 2. [`async fn` in traits][9] This would allow us to drop the `async_trait` dependency, but it is currently still required by 3 other dependencies. Build time in debug mode (on my own PC) is cut down from 256s to 189s (for a clean build). Build time in release mode is cut down from 473s to 391s (for a clean build). Binary sizes only change minimally (825 MB -> 807 MB in debug, 29 MB -> 30 MB in release). [8]: https://github.com/rust-lang/rust/pull/116505 [9]: https://github.com/rust-lang/rust/pull/115822/ * chore: Apply rustfmt. * CHANGELOG: Add PR #3039. * feat(plugins): introduce 'pipes', allowing users to pipe data to and control plugins from the command line (#3066) * prototype - working with message from the cli * prototype - pipe from the CLI to plugins * prototype - pipe from the CLI to plugins and back again * prototype - working with better cli interface * prototype - working after removing unused stuff * prototype - working with launching plugin if it is not launched, also fixed event ordering * refactor: change message to cli-message * prototype - allow plugins to send messages to each other * fix: allow cli messages to send plugin parameters (and implement backpressure) * fix: use input_pipe_id to identify cli pipes instead of their message name * fix: come cleanups and add skip_cache parameter * fix: pipe/client-server communication robustness * fix: leaking messages between plugins while loading * feat: allow plugins to specify how a new plugin instance is launched when sending messages * fix: add permissions * refactor: adjust cli api * fix: improve cli plugin loading error messages * docs: cli pipe * fix: take plugin configuration into account when messaging between plugins * refactor: pipe message protobuf interface * refactor: update(event) -> pipe * refactor - rename CliMessage to CliPipe * fix: add is_private to pipes and change some naming * refactor - cli client * refactor: various cleanups * style(fmt): rustfmt * fix(pipes): backpressure across multiple plugins * style: some cleanups * style(fmt): rustfmt * style: fix merge conflict mistake * style(wording): clarify pipe permission * docs(changelog): introduce pipes * xtask: Disable pusing during publish (#3040) * xtask: Add `--no-push` flag to `publish` which can be used when simulating releases to work without a writable git fork of the zellij code. * xtask: Fix borrow issues * xtask/pipe: Require lockfile in publish to avoid errors from invalid dependency versions. * CHANGELOG: Add PR #3040. * fix(terminal): some real/saved cursor bugs during resize (#3032) * refactor: Simplify transfer_rows_from_viewport_to_lines_above next_lines is always consolidated to a single Row, which immediately gets removed - we can remove some dead code as a result * perf: Batch remove rows from the viewport for performance Given a 1MB line catted into the terminal, a toggle-fullscreen + toggle-fullscreen + close-pane + `run true` goes from ~9s to ~3s * perf: Optimize Row::drain_until by splitting chars in one step Given a 10MB line catted into the terminal, a toggle-fullscreen + toggle-fullscreen + close-pane + `run true` goes from ~23s to ~20s * refactor: Simplify `if let` into a `.map` * refactor: There are only new saved coordinates when there were old ones * refactor: Unify viewport transfer: use common variable names * fix: Use same saved cursor logic in height resize as width See #2182 for original introduction that only added it in one branch, this fixes an issue where the saved cursor was incorrectly reset when the real cursor was * fix: Correct saved+real cursor calculations when reflowing long lines * fix: Don't create canonical lines if cursor ends on EOL after resize Previously if a 20 character line were split into two 10 character lines, the cursor would be placed on the line after the two lines. New characters would then be treated as a new canonical line. This commit fixes this by biasing cursors to the end of the previous line. * fix: for cursor index calculation in lines that are already wrapped * chore: test for real/saved cursor position being handled separately * chore: Apply cargo format * chore(repo): update issue templates * Bump rust version to 1.75.0 (#3039) * rust-toolchain: Bump toolchain version to 1.69.0 which, compared to the previous 1.67.0, has the following impacts on `zellij`: - [Turn off debuginfo for build deps][2]: Increases build time (on my machine) from ~230 s in 1.67.0 to ~250 s now, *which is unexpected* This version also changes [handling of the `default-features` flag][3] when specifying dependencies in `Cargo.toml`. If a dependent crate requires `default-features = true` on a crate that is required as `default-features = false` further up the dependency tree, the `true` setting "wins". We only specify `default-features = false` for three crates total: - `names`: This is used only by us - `surf`: This is used only by us - `vte`: This is also required by `strip-ansi-escapes`, but that has `default-features = false` as well How this affects our transitive dependencies is unknown at this point. [2]: https://github.com/rust-lang/cargo/pull/11252/ [3]: https://github.com/rust-lang/cargo/pull/11409/ * rust-toolchain: Bump toolchain version to 1.70.0 which, compared to the previous 1.69.0, as the following impacts on `zellij`: 1. [Enable sparse registry checkout for crates.io by default][1] This drastically increases the time to first build on a fresh rust installation/a rust installation with a clean cargo registry cache. Previously it took about 75s to populate the deps/cache (with `cargo fetch --locked` and ~100 MBit/s network), whereas now the same process takes ~10 s. 2. [The `OnceCell` type is now part of std][2] In theory, this would allow us to cut a dependency from `zellij-utils`, but the `once_cell` crate is pulled in by another 16 deps, so there's no point in attempting it right now. Build times and binary sizes are unaffected by this change compared to the previous 1.69.0 toolchain. [1]: https://github.com/rust-lang/cargo/pull/11791/ [2]: https://doc.rust-lang.org/stable/std/cell/struct.OnceCell.html * rust-toolchain: Bump toolchain version to 1.75.0 which, compared to the previous 1.70.0, has the following impacts on `zellij`: 1. [cross-crate inlining][8] This should increase application performance, as functions can now be inlined across crates. 2. [`async fn` in traits][9] This would allow us to drop the `async_trait` dependency, but it is currently still required by 3 other dependencies. Build time in debug mode (on my own PC) is cut down from 256s to 189s (for a clean build). Build time in release mode is cut down from 473s to 391s (for a clean build). Binary sizes only change minimally (825 MB -> 807 MB in debug, 29 MB -> 30 MB in release). [8]: https://github.com/rust-lang/rust/pull/116505 [9]: https://github.com/rust-lang/rust/pull/115822/ * chore: Apply rustfmt. * CHANGELOG: Add PR #3039. * feat(plugins): introduce 'pipes', allowing users to pipe data to and control plugins from the command line (#3066) * prototype - working with message from the cli * prototype - pipe from the CLI to plugins * prototype - pipe from the CLI to plugins and back again * prototype - working with better cli interface * prototype - working after removing unused stuff * prototype - working with launching plugin if it is not launched, also fixed event ordering * refactor: change message to cli-message * prototype - allow plugins to send messages to each other * fix: allow cli messages to send plugin parameters (and implement backpressure) * fix: use input_pipe_id to identify cli pipes instead of their message name * fix: come cleanups and add skip_cache parameter * fix: pipe/client-server communication robustness * fix: leaking messages between plugins while loading * feat: allow plugins to specify how a new plugin instance is launched when sending messages * fix: add permissions * refactor: adjust cli api * fix: improve cli plugin loading error messages * docs: cli pipe * fix: take plugin configuration into account when messaging between plugins * refactor: pipe message protobuf interface * refactor: update(event) -> pipe * refactor - rename CliMessage to CliPipe * fix: add is_private to pipes and change some naming * refactor - cli client * refactor: various cleanups * style(fmt): rustfmt * fix(pipes): backpressure across multiple plugins * style: some cleanups * style(fmt): rustfmt * style: fix merge conflict mistake * style(wording): clarify pipe permission * docs(changelog): introduce pipes * fix: add some robustness and future proofing * fix e2e tests --------- Co-authored-by: Aram Drevekenin <aram@poor.dev> Co-authored-by: har7an <99636919+har7an@users.noreply.github.com> * fix integer overflow again (oops) --------- Co-authored-by: Aram Drevekenin <aram@poor.dev> Co-authored-by: har7an <99636919+har7an@users.noreply.github.com>
-rw-r--r--xtask/src/build.rs30
-rw-r--r--zellij-server/src/output/mod.rs32
-rw-r--r--zellij-server/src/panes/grid.rs264
-rw-r--r--zellij-server/src/panes/terminal_character.rs138
-rw-r--r--zellij-server/src/panes/terminal_pane.rs6
-rw-r--r--zellij-server/src/ui/boundaries.rs10
-rw-r--r--zellij-server/src/ui/pane_boundaries_frame.rs56
7 files changed, 298 insertions, 238 deletions
diff --git a/xtask/src/build.rs b/xtask/src/build.rs
index 4babad0c3..67547a1c2 100644
--- a/xtask/src/build.rs
+++ b/xtask/src/build.rs
@@ -52,22 +52,38 @@ pub fn build(sh: &Shell, flags: flags::Build) -> anyhow::Result<()> {
std::fs::create_dir_all(&prost_asset_dir).unwrap();
let mut prost = prost_build::Config::new();
+ let last_generated = prost_asset_dir
+ .join("generated_plugin_api.rs")
+ .metadata()
+ .and_then(|m| m.modified());
+ let mut needs_regeneration = false;
prost.out_dir(prost_asset_dir);
prost.include_file("generated_plugin_api.rs");
let mut proto_files = vec![];
for entry in std::fs::read_dir(&protobuf_source_dir).unwrap() {
let entry_path = entry.unwrap().path();
if entry_path.is_file() {
- if let Some(extension) = entry_path.extension() {
- if extension == "proto" {
- proto_files.push(entry_path.display().to_string())
- }
+ if !entry_path
+ .extension()
+ .map(|e| e == "proto")
+ .unwrap_or(false)
+ {
+ continue;
+ }
+ proto_files.push(entry_path.display().to_string());
+ let modified = entry_path.metadata().and_then(|m| m.modified());
+ needs_regeneration |= match (&last_generated, modified) {
+ (Ok(last_generated), Ok(modified)) => modified >= *last_generated,
+ // Couldn't read some metadata, assume needs update
+ _ => true,
}
}
}
- prost
- .compile_protos(&proto_files, &[protobuf_source_dir])
- .unwrap();
+ if needs_regeneration {
+ prost
+ .compile_protos(&proto_files, &[protobuf_source_dir])
+ .unwrap();
+ }
}
let _pd = sh.push_dir(Path::new(crate_name));
diff --git a/zellij-server/src/output/mod.rs b/zellij-server/src/output/mod.rs
index 3f99b4013..7ad7a38bc 100644
--- a/zellij-server/src/output/mod.rs
+++ b/zellij-server/src/output/mod.rs
@@ -6,7 +6,7 @@ use crate::panes::Row;
use crate::{
panes::sixel::SixelImageStore,
panes::terminal_character::{AnsiCode, CharacterStyles},
- panes::{LinkHandler, TerminalCharacter, EMPTY_TERMINAL_CHARACTER},
+ panes::{LinkHandler, TerminalCharacter, DEFAULT_STYLES, EMPTY_TERMINAL_CHARACTER},
ClientId,
};
use std::cell::RefCell;
@@ -91,14 +91,13 @@ fn serialize_chunks_with_newlines(
let link_handler = link_handler.map(|l_h| l_h.borrow());
for character_chunk in character_chunks {
let chunk_changed_colors = character_chunk.changed_colors();
- let mut character_styles =
- CharacterStyles::new().enable_styled_underlines(styled_underlines);
+ let mut character_styles = DEFAULT_STYLES.enable_styled_underlines(styled_underlines);
vte_output.push_str("\n\r");
let mut chunk_width = character_chunk.x;
for t_character in character_chunk.terminal_characters.iter() {
let current_character_styles = adjust_styles_for_possible_selection(
character_chunk.selection_and_colors(),
- t_character.styles,
+ *t_character.styles,
character_chunk.y,
chunk_width,
);
@@ -110,10 +109,9 @@ fn serialize_chunks_with_newlines(
&mut vte_output,
)
.with_context(err_context)?;
- chunk_width += t_character.width;
+ chunk_width += t_character.width();
vte_output.push(t_character.character);
}
- character_styles.clear();
}
Ok(vte_output)
}
@@ -131,15 +129,14 @@ fn serialize_chunks(
let link_handler = link_handler.map(|l_h| l_h.borrow());
for character_chunk in character_chunks {
let chunk_changed_colors = character_chunk.changed_colors();
- let mut character_styles =
- CharacterStyles::new().enable_styled_underlines(styled_underlines);
+ let mut character_styles = DEFAULT_STYLES.enable_styled_underlines(styled_underlines);
vte_goto_instruction(character_chunk.x, character_chunk.y, &mut vte_output)
.with_context(err_context)?;
let mut chunk_width = character_chunk.x;
for t_character in character_chunk.terminal_characters.iter() {
let current_character_styles = adjust_styles_for_possible_selection(
character_chunk.selection_and_colors(),
- t_character.styles,
+ *t_character.styles,
character_chunk.y,
chunk_width,
);
@@ -151,10 +148,9 @@ fn serialize_chunks(
&mut vte_output,
)
.with_context(err_context)?;
- chunk_width += t_character.width;
+ chunk_width += t_character.width();
vte_output.push(t_character.character);
}
- character_styles.clear();
}
if let Some(sixel_image_store) = sixel_image_store {
if let Some(sixel_chunks) = sixel_chunks {
@@ -215,7 +211,7 @@ fn adjust_middle_segment_for_wide_chars(
let mut pad_left_end_by = 0;
let mut pad_right_start_by = 0;
for (absolute_index, t_character) in terminal_characters.iter().enumerate() {
- current_x += t_character.width;
+ current_x += t_character.width();
if current_x >= middle_start && absolute_middle_start_index.is_none() {
if current_x > middle_start {
pad_left_end_by = current_x - middle_start;
@@ -802,7 +798,7 @@ impl CharacterChunk {
pub fn width(&self) -> usize {
let mut width = 0;
for t_character in &self.terminal_characters {
- width += t_character.width
+ width += t_character.width()
}
width
}
@@ -814,14 +810,14 @@ impl CharacterChunk {
break;
}
let next_character = self.terminal_characters.remove(0); // TODO: consider copying self.terminal_characters into a VecDeque to make this process faster?
- if drained_part_len + next_character.width <= x {
+ if drained_part_len + next_character.width() <= x {
+ drained_part_len += next_character.width();
drained_part.push_back(next_character);
- drained_part_len += next_character.width;
} else {
if drained_part_len == x {
self.terminal_characters.insert(0, next_character); // put it back
- } else if next_character.width > 1 {
- for _ in 1..next_character.width {
+ } else if next_character.width() > 1 {
+ for _ in 1..next_character.width() {
self.terminal_characters.insert(0, EMPTY_TERMINAL_CHARACTER);
drained_part.push_back(EMPTY_TERMINAL_CHARACTER);
}
@@ -963,7 +959,7 @@ impl OutputBuffer {
row: &Row,
viewport_width: usize,
) -> Vec<TerminalCharacter> {
- let mut terminal_characters: Vec<TerminalCharacter> = row.columns.iter().copied().collect();
+ let mut terminal_characters: Vec<TerminalCharacter> = row.columns.iter().cloned().collect();
// pad row
let row_width = row.width();
if row_width < viewport_width {
diff --git a/zellij-server/src/panes/grid.rs b/zellij-server/src/panes/grid.rs
index f74e2c07b..3109cb10f 100644
--- a/zellij-server/src/panes/grid.rs
+++ b/zellij-server/src/panes/grid.rs
@@ -3,7 +3,6 @@ use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
-use unicode_width::UnicodeWidthChar;
use zellij_utils::data::Style;
use zellij_utils::errors::prelude::*;
use zellij_utils::regex::Regex;
@@ -35,7 +34,7 @@ use crate::panes::link_handler::LinkHandler;
use crate::panes::search::SearchResult;
use crate::panes::selection::Selection;
use crate::panes::terminal_character::{
- AnsiCode, CharacterStyles, CharsetIndex, Cursor, CursorShape, StandardCharset,
+ AnsiCode, CharsetIndex, Cursor, CursorShape, RcCharacterStyles, StandardCharset,
TerminalCharacter, EMPTY_TERMINAL_CHARACTER,
};
use crate::ui::components::UiComponentParser;
@@ -416,11 +415,8 @@ impl Debug for Grid {
for y in image_top_edge..image_bottom_edge {
let row = buffer.get_mut(y).unwrap();
for x in image_left_edge..image_right_edge {
- let fake_sixel_terminal_character = TerminalCharacter {
- character: sixel_indication_character(x),
- width: 1,
- styles: Default::default(),
- };
+ let fake_sixel_terminal_character =
+ TerminalCharacter::new_singlewidth(sixel_indication_character(x));
row.add_character_at(fake_sixel_terminal_character, x);
}
}
@@ -517,7 +513,7 @@ impl Grid {
pub fn update_line_for_rendering(&mut self, line_index: usize) {
self.output_buffer.update_line(line_index);
}
- pub fn advance_to_next_tabstop(&mut self, styles: CharacterStyles) {
+ pub fn advance_to_next_tabstop(&mut self, styles: RcCharacterStyles) {
let next_tabstop = self
.horizontal_tabstops
.iter()
@@ -769,7 +765,7 @@ impl Grid {
}
self.selection.reset();
self.sixel_grid.character_cell_size_possibly_changed();
- if new_columns != self.width {
+ let cursors = if new_columns != self.width {
self.horizontal_tabstops = create_horizontal_tabstops(new_columns);
let mut cursor_canonical_line_index = self.cursor_canonical_line_index();
let cursor_index_in_canonical_line = self.cursor_index_in_canonical_line();
@@ -885,78 +881,42 @@ impl Grid {
},
_ => None,
};
-
- let current_viewport_row_count = self.viewport.len();
- match current_viewport_row_count.cmp(&self.height) {
- Ordering::Less => {
- let row_count_to_transfer = self.height - current_viewport_row_count;
-
- transfer_rows_from_lines_above_to_viewport(
- &mut self.lines_above,
- &mut self.viewport,
- &mut self.sixel_grid,
- row_count_to_transfer,
- new_columns,
- );
- let rows_pulled = self.viewport.len() - current_viewport_row_count;
- new_cursor_y += rows_pulled;
- if let Some(saved_cursor_y_coordinates) = saved_cursor_y_coordinates.as_mut() {
- *saved_cursor_y_coordinates += rows_pulled;
- }
- },
- Ordering::Greater => {
- let row_count_to_transfer = current_viewport_row_count - self.height;
- if row_count_to_transfer > new_cursor_y {
- new_cursor_y = 0;
- } else {
- new_cursor_y -= row_count_to_transfer;
- }
- if let Some(saved_cursor_y_coordinates) = saved_cursor_y_coordinates.as_mut() {
- if row_count_to_transfer > *saved_cursor_y_coordinates {
- *saved_cursor_y_coordinates = 0;
- } else {
- *saved_cursor_y_coordinates -= row_count_to_transfer;
- }
- }
- transfer_rows_from_viewport_to_lines_above(
- &mut self.viewport,
- &mut self.lines_above,
- &mut self.sixel_grid,
- row_count_to_transfer,
- new_columns,
- );
- },
- Ordering::Equal => {},
- }
- self.cursor.y = new_cursor_y;
- self.cursor.x = new_cursor_x;
- if let Some(saved_cursor_position) = self.saved_cursor_position.as_mut() {
- match (saved_cursor_x_coordinates, saved_cursor_y_coordinates) {
- (Some(saved_cursor_x_coordinates), Some(saved_cursor_y_coordinates)) => {
- saved_cursor_position.x = saved_cursor_x_coordinates;
- saved_cursor_position.y = saved_cursor_y_coordinates;
- },
- _ => log::error!(
- "invalid state - cannot set saved cursor to {:?} {:?}",
- saved_cursor_x_coordinates,
- saved_cursor_y_coordinates
- ),
- }
- };
- }
- if new_rows != self.height {
- let mut new_cursor_y = self.cursor.y;
- let mut saved_cursor_y_coordinates = self
+ Some((
+ new_cursor_y,
+ saved_cursor_y_coordinates,
+ new_cursor_x,
+ saved_cursor_x_coordinates,
+ ))
+ } else if new_rows != self.height {
+ let saved_cursor_y_coordinates = self
.saved_cursor_position
.as_ref()
.map(|saved_cursor| saved_cursor.y);
-
- let new_cursor_x = self.cursor.x;
let saved_cursor_x_coordinates = self
.saved_cursor_position
.as_ref()
.map(|saved_cursor| saved_cursor.x);
+ Some((
+ self.cursor.y,
+ saved_cursor_y_coordinates,
+ self.cursor.x,
+ saved_cursor_x_coordinates,
+ ))
+ } else {
+ None
+ };
+
+ if let Some(cursors) = cursors {
+ // At this point the x coordinates have been calculated, the y coordinates
+ // will be updated within this block
+ let (
+ mut new_cursor_y,
+ mut saved_cursor_y_coordinates,
+ new_cursor_x,
+ saved_cursor_x_coordinates,
+ ) = cursors;
+
let current_viewport_row_count = self.viewport.len();
match current_viewport_row_count.cmp(&new_rows) {
Ordering::Less => {
@@ -1035,7 +995,7 @@ impl Grid {
.iter()
.map(|r| {
let excess_width = r.excess_width();
- let mut line: Vec<TerminalCharacter> = r.columns.iter().copied().collect();
+ let mut line: Vec<TerminalCharacter> = r.columns.iter().cloned().collect();
// pad line
line.resize(
self.width.saturating_sub(excess_width),
@@ -1234,7 +1194,7 @@ impl Grid {
self.viewport.remove(scroll_region_bottom);
}
let mut pad_character = EMPTY_TERMINAL_CHARACTER;
- pad_character.styles = self.cursor.pending_styles;
+ pad_character.styles = self.cursor.pending_styles.clone();
let columns = VecDeque::from(vec![pad_character; self.width]);
self.viewport
.insert(scroll_region_top, Row::from_columns(columns).canonical());
@@ -1250,10 +1210,10 @@ impl Grid {
{
self.pad_lines_until(scroll_region_bottom, EMPTY_TERMINAL_CHARACTER);
let mut pad_character = EMPTY_TERMINAL_CHARACTER;
- pad_character.styles = self.cursor.pending_styles;
+ pad_character.styles = self.cursor.pending_styles.clone();
for _ in 0..count {
self.viewport.remove(scroll_region_top);
- let columns = VecDeque::from(vec![pad_character; self.width]);
+ let columns = VecDeque::from(vec![pad_character.clone(); self.width]);
self.viewport
.insert(scroll_region_bottom, Row::from_columns(columns).canonical());
}
@@ -1268,7 +1228,7 @@ impl Grid {
};
for _ in 0..self.height {
- let columns = VecDeque::from(vec![character; self.width]);
+ let columns = VecDeque::from(vec![character.clone(); self.width]);
self.viewport.push(Row::from_columns(columns).canonical());
}
self.output_buffer.update_all_lines();
@@ -1294,14 +1254,14 @@ impl Grid {
}
let mut pad_character = EMPTY_TERMINAL_CHARACTER;
- pad_character.styles = self.cursor.pending_styles;
+ pad_character.styles = self.cursor.pending_styles.clone();
let columns = VecDeque::from(vec![pad_character; self.width]);
self.viewport.push(Row::from_columns(columns).canonical());
self.selection.move_up(1);
} else {
self.viewport.remove(scroll_region_top);
let mut pad_character = EMPTY_TERMINAL_CHARACTER;
- pad_character.styles = self.cursor.pending_styles;
+ pad_character.styles = self.cursor.pending_styles.clone();
let columns = VecDeque::from(vec![pad_character; self.width]);
if self.viewport.len() >= scroll_region_bottom {
self.viewport
@@ -1393,7 +1353,7 @@ impl Grid {
}
}
pub fn add_character(&mut self, terminal_character: TerminalCharacter) {
- let character_width = terminal_character.width;
+ let character_width = terminal_character.width();
// Drop zero-width Unicode/UTF-8 codepoints, like for example Variation Selectors.
// This breaks unicode grapheme segmentation, and is the reason why some characters
// aren't displayed correctly. Refer to this issue for more information:
@@ -1415,7 +1375,7 @@ impl Grid {
self.viewport