summaryrefslogtreecommitdiffstats
path: root/zellij-server/src
diff options
context:
space:
mode:
Diffstat (limited to 'zellij-server/src')
-rw-r--r--zellij-server/src/panes/grid.rs181
-rw-r--r--zellij-server/src/panes/plugin_pane.rs3
-rw-r--r--zellij-server/src/panes/terminal_character.rs14
-rw-r--r--zellij-server/src/panes/terminal_pane.rs3
-rw-r--r--zellij-server/src/panes/unit/grid_tests.rs159
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__bash_delete_wide_characters.snap9
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__delete_wide_character_under_cursor.snap8
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__delete_wide_characters_before_cursor.snap7
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__delete_wide_characters_before_cursor_when_cursor_is_on_wide_character.snap7
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__fish_wide_characters_override_clock.snap8
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__replace_wide_character_under_cursor.snap8
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__unwrap_wide_characters_on_size_change.snap27
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest8_1.snap2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest8_2.snap2
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest8_3.snap48
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__vttest8_4.snap48
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__wide_characters.snap8
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__wide_characters_line_wrap.snap10
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__wrap_wide_characters.snap27
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__wrap_wide_characters_at_the_end_of_the_line.snap27
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__wrap_wide_characters_in_the_middle_of_the_line.snap27
-rw-r--r--zellij-server/src/panes/unit/snapshots/zellij_server__panes__grid__grid_tests__wrap_wide_characters_on_size_change.snap27
-rw-r--r--zellij-server/src/tab.rs11
23 files changed, 554 insertions, 117 deletions
diff --git a/zellij-server/src/panes/grid.rs b/zellij-server/src/panes/grid.rs
index ac347db0e..14bdbcd16 100644
--- a/zellij-server/src/panes/grid.rs
+++ b/zellij-server/src/panes/grid.rs
@@ -1,3 +1,5 @@
+use unicode_width::UnicodeWidthChar;
+
use std::{
cmp::Ordering,
collections::{BTreeSet, VecDeque},
@@ -251,9 +253,6 @@ impl Grid {
colors,
}
}
- pub fn contains_widechar(&self) -> bool {
- self.viewport.iter().any(|c| c.contains_widechar())
- }
pub fn advance_to_next_tabstop(&mut self, styles: CharacterStyles) {
let mut next_tabstop = None;
for tabstop in self.horizontal_tabstops.iter() {
@@ -421,12 +420,12 @@ impl Grid {
canonical_line_parts.push(Row::new().canonical());
}
while !canonical_line.columns.is_empty() {
- let next_wrap = if canonical_line.len() > new_columns {
- canonical_line.columns.drain(..new_columns)
+ let next_wrap = if canonical_line.width() > new_columns {
+ canonical_line.drain_until(new_columns)
} else {
- canonical_line.columns.drain(..)
+ canonical_line.columns.drain(..).collect()
};
- let row = Row::from_columns(next_wrap.collect());
+ let row = Row::from_columns(next_wrap);
// if there are no more parts, this row is canonical as long as it originally
// was canonical (it might not have been for example if it's the first row in
// the viewport, and the actual canonical row is above it in the scrollback)
@@ -522,9 +521,13 @@ impl Grid {
.viewport
.iter()
.map(|r| {
+ let excess_width = r.excess_width();
let mut line: Vec<TerminalCharacter> = r.columns.iter().copied().collect();
// pad line
- line.resize(self.width, EMPTY_TERMINAL_CHARACTER);
+ line.resize(
+ self.width.saturating_sub(excess_width),
+ EMPTY_TERMINAL_CHARACTER,
+ );
line
})
.collect();
@@ -657,7 +660,11 @@ impl Grid {
}
}
}
- pub fn add_character_at_cursor_position(&mut self, terminal_character: TerminalCharacter) {
+ pub fn add_character_at_cursor_position(
+ &mut self,
+ terminal_character: TerminalCharacter,
+ max_width: usize,
+ ) {
match self.viewport.get_mut(self.cursor.y) {
Some(row) => {
if self.insert_mode {
@@ -665,6 +672,7 @@ impl Grid {
} else {
row.add_character_at(terminal_character, self.cursor.x);
}
+ row.truncate(max_width);
}
None => {
// pad lines until cursor if they do not exist
@@ -678,6 +686,7 @@ impl Grid {
}
pub fn add_character(&mut self, terminal_character: TerminalCharacter) {
// TODO: try to separate adding characters from moving the cursors in this function
+ let character_width = terminal_character.width;
if self.cursor.x >= self.width {
if self.disable_linewrap {
return;
@@ -703,8 +712,8 @@ impl Grid {
}
}
}
- self.add_character_at_cursor_position(terminal_character);
- self.move_cursor_forward_until_edge(1);
+ self.add_character_at_cursor_position(terminal_character, self.width);
+ self.move_cursor_forward_until_edge(character_width);
}
pub fn move_cursor_forward_until_edge(&mut self, count: usize) {
let count_to_move = std::cmp::min(count, self.width - (self.cursor.x));
@@ -714,19 +723,11 @@ impl Grid {
self.viewport
.get_mut(self.cursor.y)
.unwrap()
- .truncate(self.cursor.x);
- if self.cursor.x < self.width - 1 {
- let mut characters_to_append = vec![replace_with; self.width - self.cursor.x];
- self.viewport
- .get_mut(self.cursor.y)
- .unwrap()
- .append(&mut characters_to_append);
- }
+ .replace_and_pad_end(self.cursor.x, self.width, replace_with);
}
pub fn replace_characters_in_line_before_cursor(&mut self, replace_with: TerminalCharacter) {
- let line_part = vec![replace_with; self.cursor.x + 1];
let row = self.viewport.get_mut(self.cursor.y).unwrap();
- row.replace_beginning_with(line_part);
+ row.replace_and_pad_beginning(self.cursor.x, replace_with);
}
pub fn clear_all_after_cursor(&mut self, replace_with: TerminalCharacter) {
if let Some(cursor_row) = self.viewport.get_mut(self.cursor.y) {
@@ -946,13 +947,15 @@ impl Grid {
empty_character.styles = empty_char_style;
let current_row = self.viewport.get_mut(self.cursor.y).unwrap();
for _ in 0..count {
- current_row.delete_character(self.cursor.x);
+ let deleted_character = current_row.delete_and_return_character(self.cursor.x);
+ let excess_width = deleted_character
+ .map(|terminal_character| terminal_character.width)
+ .unwrap_or(0)
+ .saturating_sub(1);
+ for _ in 0..excess_width {
+ current_row.insert_character_at(empty_character, self.cursor.x);
+ }
}
- let mut empty_space_to_append = vec![empty_character; count];
- self.viewport
- .get_mut(self.cursor.y)
- .unwrap()
- .append(&mut empty_space_to_append);
}
fn add_newline(&mut self) {
self.add_canonical_line();
@@ -988,6 +991,7 @@ impl Perform for Grid {
// is a little faster
let terminal_character = TerminalCharacter {
character: c,
+ width: c.width().unwrap_or(0),
styles: self.cursor.pending_styles,
};
self.set_preceding_character(terminal_character);
@@ -1628,20 +1632,47 @@ impl Row {
self.is_canonical = true;
self
}
- pub fn contains_widechar(&self) -> bool {
- self.columns.iter().any(|c| c.is_widechar())
+ pub fn width(&self) -> usize {
+ let mut width = 0;
+ for terminal_character in self.columns.iter() {
+ width += terminal_character.width;
+ }
+ width
+ }
+ pub fn excess_width(&self) -> usize {
+ let mut acc = 0;
+ for terminal_character in self.columns.iter() {
+ if terminal_character.width > 1 {
+ acc += terminal_character.width - 1;
+ }
+ }
+ acc
+ }
+ pub fn excess_width_until(&self, x: usize) -> usize {
+ let mut acc = 0;
+ for terminal_character in self.columns.iter().take(x) {
+ if terminal_character.width > 1 {
+ acc += terminal_character.width - 1;
+ }
+ }
+ acc
}
pub fn add_character_at(&mut self, terminal_character: TerminalCharacter, x: usize) {
- match self.columns.len().cmp(&x) {
- Ordering::Equal => self.columns.push(terminal_character),
+ match self.width().cmp(&x) {
+ Ordering::Equal => {
+ self.columns.push(terminal_character);
+ }
Ordering::Less => {
- self.columns.resize(x, EMPTY_TERMINAL_CHARACTER);
+ let width_offset = self.excess_width_until(x);
+ self.columns
+ .resize(x.saturating_sub(width_offset), EMPTY_TERMINAL_CHARACTER);
self.columns.push(terminal_character);
}
Ordering::Greater => {
+ let width_offset = self.excess_width_until(x);
// this is much more performant than remove/insert
self.columns.push(terminal_character);
- self.columns.swap_remove(x);
+ self.columns.swap_remove(x.saturating_sub(width_offset));
}
}
}
@@ -1661,7 +1692,11 @@ impl Row {
// this is much more performant than remove/insert
if x < self.columns.len() {
self.columns.push(terminal_character);
- self.columns.swap_remove(x);
+ let character = self.columns.swap_remove(x);
+ let excess_width = character.width.saturating_sub(1);
+ for _ in 0..excess_width {
+ self.columns.insert(x, terminal_character);
+ }
}
}
pub fn replace_columns(&mut self, columns: Vec<TerminalCharacter>) {
@@ -1671,12 +1706,77 @@ impl Row {
self.columns.push(terminal_character);
}
pub fn truncate(&mut self, x: usize) {
- self.columns.truncate(x);
+ let width_offset = self.excess_width_until(x);
+ let truncate_position = x.saturating_sub(width_offset);
+ if truncate_position < self.columns.len() {
+ self.columns.truncate(truncate_position);
+ }
+ }
+ pub fn position_accounting_for_widechars(&self, x: usize) -> usize {
+ let mut position = x;
+ for (index, terminal_character) in self.columns.iter().enumerate() {
+ if index == position {
+ break;
+ }
+ if terminal_character.width > 1 {
+ position = position.saturating_sub(terminal_character.width.saturating_sub(1));
+ }
+ }
+ position
+ }
+ pub fn replace_and_pad_end(
+ &mut self,
+ from: usize,
+ to: usize,
+ terminal_character: TerminalCharacter,
+ ) {
+ let from_position_accounting_for_widechars = self.position_accounting_for_widechars(from);
+ let to_position_accounting_for_widechars = self.position_accounting_for_widechars(to);
+ let replacement_length = to_position_accounting_for_widechars
+ .saturating_sub(from_position_accounting_for_widechars);
+ let mut replace_with = vec![terminal_character; replacement_length];
+ self.columns
+ .truncate(from_position_accounting_for_widechars);
+ self.columns.append(&mut replace_with);
}
pub fn append(&mut self, to_append: &mut Vec<TerminalCharacter>) {
self.columns.append(to_append);
}
+ pub fn drain_until(&mut self, x: usize) -> Vec<TerminalCharacter> {
+ let mut drained_part: Vec<TerminalCharacter> = vec![];
+ let mut drained_part_len = 0;
+ loop {
+ if self.columns.is_empty() {
+ break;
+ }
+ let next_character_len = self.columns.get(0).unwrap().width;
+ if drained_part_len + next_character_len <= x {
+ drained_part.push(self.columns.remove(0));
+ drained_part_len += next_character_len;
+ } else {
+ break;
+ }
+ }
+ drained_part
+ }
+ pub fn replace_and_pad_beginning(&mut self, to: usize, terminal_character: TerminalCharacter) {
+ let to_position_accounting_for_widechars = self.position_accounting_for_widechars(to);
+ let width_of_current_character = self
+ .columns
+ .get(to_position_accounting_for_widechars)
+ .map(|character| character.width)
+ .unwrap_or(1);
+ let mut replace_with = vec![terminal_character; to + width_of_current_character];
+ if to_position_accounting_for_widechars > self.columns.len() {
+ self.columns.clear();
+ } else {
+ drop(self.columns.drain(0..=to_position_accounting_for_widechars));
+ }
+ replace_with.append(&mut self.columns);
+ self.columns = replace_with;
+ }
pub fn replace_beginning_with(&mut self, mut line_part: Vec<TerminalCharacter>) {
+ // this assumes line_part has no wide characters
if line_part.len() > self.columns.len() {
self.columns.clear();
} else {
@@ -1691,20 +1791,25 @@ impl Row {
pub fn is_empty(&self) -> bool {
self.columns.is_empty()
}
- pub fn delete_character(&mut self, x: usize) {
+ pub fn delete_and_return_character(&mut self, x: usize) -> Option<TerminalCharacter> {
if x < self.columns.len() {
- self.columns.remove(x);
+ Some(self.columns.remove(x))
+ } else {
+ None
}
}
pub fn split_to_rows_of_length(&mut self, max_row_length: usize) -> Vec<Row> {
let mut parts: Vec<Row> = vec![];
let mut current_part: Vec<TerminalCharacter> = vec![];
+ let mut current_part_len = 0;
for character in self.columns.drain(..) {
- if current_part.len() == max_row_length {
+ if current_part_len + character.width > max_row_length {
parts.push(Row::from_columns(current_part));
current_part = vec![];
+ current_part_len = 0;
}
current_part.push(character);
+ current_part_len += character.width;
}
if !current_part.is_empty() {
parts.push(Row::from_columns(current_part))
diff --git a/zellij-server/src/panes/plugin_pane.rs b/zellij-server/src/panes/plugin_pane.rs
index 5549a388f..e5c5e15de 100644
--- a/zellij-server/src/panes/plugin_pane.rs
+++ b/zellij-server/src/panes/plugin_pane.rs
@@ -99,9 +99,6 @@ impl Pane for PluginPane {
fn position_and_size_override(&self) -> Option<PositionAndSize> {
self.position_and_size_override
}
- fn contains_widechar(&self) -> bool {
- false
- }
fn should_render(&self) -> bool {
self.should_render
}
diff --git a/zellij-server/src/panes/terminal_character.rs b/zellij-server/src/panes/terminal_character.rs
index e6489eb42..ececb2a73 100644
--- a/zellij-server/src/panes/terminal_character.rs
+++ b/zellij-server/src/panes/terminal_character.rs
@@ -1,5 +1,3 @@
-use unicode_width::UnicodeWidthChar;
-
use std::convert::TryFrom;
use std::fmt::{self, Debug, Display, Formatter};
use std::ops::{Index, IndexMut};
@@ -8,6 +6,7 @@ use zellij_utils::vte::ParamsIter;
pub const EMPTY_TERMINAL_CHARACTER: TerminalCharacter = TerminalCharacter {
character: ' ',
+ width: 1,
styles: CharacterStyles {
foreground: Some(AnsiCode::Reset),
background: Some(AnsiCode::Reset),
@@ -750,6 +749,7 @@ impl Cursor {
pub struct TerminalCharacter {
pub character: char,
pub styles: CharacterStyles,
+ pub width: usize,
}
impl ::std::fmt::Debug for TerminalCharacter {
@@ -758,16 +758,6 @@ impl ::std::fmt::Debug for TerminalCharacter {
}
}
-impl TerminalCharacter {
- pub fn width(&self) -> usize {
- self.character.width().unwrap_or(0)
- }
-
- pub fn is_widechar(&self) -> bool {
- self.width() > 1
- }
-}
-
fn parse_sgr_color(params: &mut dyn Iterator<Item = u16>) -> Option<AnsiCode> {
match params.next() {
Some(2) => Some(AnsiCode::RgbCode((
diff --git a/zellij-server/src/panes/terminal_pane.rs b/zellij-server/src/panes/terminal_pane.rs
index 7e45c77bb..32910618d 100644
--- a/zellij-server/src/panes/terminal_pane.rs
+++ b/zellij-server/src/panes/terminal_pane.rs
@@ -123,9 +123,6 @@ impl Pane for TerminalPane {
fn position_and_size_override(&self) -> Option<PositionAndSize> {
self.position_and_size_override
}
- fn contains_widechar(&self) -> bool {
- self.grid.contains_widechar()
- }
fn should_render(&self) -> bool {
self.grid.should_render
}
diff --git a/zellij-server/src/panes/unit/grid_tests.rs b/zellij-server/src/panes/unit/grid_tests.rs
index 1a1d7f5cc..bce67e062 100644
--- a/zellij-server/src/panes/unit/grid_tests.rs
+++ b/zellij-server/src/panes/unit/grid_tests.rs
@@ -396,3 +396,162 @@ fn terminal_reports() {
}
assert_snapshot!(format!("{:?}", grid.pending_messages_to_pty));
}
+
+#[test]
+fn wide_characters() {
+ let mut vte_parser = vte::Parser::new();
+ let mut grid = Grid::new(21, 104, Palette::default());
+ let fixture_name = "wide_characters";
+ let content = read_fixture(fixture_name);
+ for byte in content {
+ vte_parser.advance(&mut grid, byte);
+ }
+ assert_snapshot!(format!("{:?}", grid));
+}
+
+#[test]
+fn wide_characters_line_wrap() {
+ let mut vte_parser = vte::Parser::new();
+ let mut grid = Grid::new(21, 104, Palette::default());
+ let fixture_name = "wide_characters_line_wrap";
+ let content = read_fixture(fixture_name);
+ for byte in content {
+ vte_parser.advance(&mut grid, byte);
+ }
+ assert_snapshot!(format!("{:?}", grid));
+}
+
+#[test]
+fn fish_wide_characters_override_clock() {
+ let mut vte_parser = vte::Parser::new();
+ let mut grid = Grid::new(21, 104, Palette::default());
+ let fixture_name = "fish_wide_characters_override_clock";
+ let content = read_fixture(fixture_name);
+ for byte in content {
+ vte_parser.advance(&mut grid, byte);
+ }
+ assert_snapshot!(format!("{:?}", grid));
+}
+
+#[test]
+fn bash_delete_wide_characters() {
+ let mut vte_parser = vte::Parser::new();
+ let mut grid = Grid::new(21, 104, Palette::default());
+ let fixture_name = "bash_delete_wide_characters";
+ let content = read_fixture(fixture_name);
+ for byte in content {
+ vte_parser.advance(&mut grid, byte);
+ }
+ assert_snapshot!(format!("{:?}", grid));
+}
+
+#[test]
+fn delete_wide_characters_before_cursor() {
+ let mut vte_parser = vte::Parser::new();
+ let mut grid = Grid::new(21, 104, Palette::default());
+ let fixture_name = "delete_wide_characters_before_cursor";
+ let content = read_fixture(fixture_name);
+ for byte in content {
+ vte_parser.advance(&mut grid, byte);
+ }
+ assert_snapshot!(format!("{:?}", grid));
+}
+
+#[test]
+fn delete_wide_characters_before_cursor_when_cursor_is_on_wide_character() {
+ let mut vte_parser = vte::Parser::new();
+ let mut grid = Grid::new(21, 104, Palette::default());
+ let fixture_name = "delete_wide_characters_before_cursor_when_cursor_is_on_wide_character";
+ let content = read_fixture(fixture_name);
+ for byte in content {
+ vte_parser.advance(&mut grid, byte);
+ }
+ assert_snapshot!(format!("{:?}", grid));
+}
+
+#[test]
+fn delete_wide_character_under_cursor() {
+ let mut vte_parser = vte::Parser::new();
+ let mut grid = Grid::new(21, 104, Palette::default());
+ let fixture_name = "delete_wide_character_under_cursor";
+ let content = read_fixture(fixture_name);
+ for byte in content {
+ vte_parser.advance(&mut grid, byte);
+ }
+ assert_snapshot!(format!("{:?}", grid));
+}
+
+#[test]
+fn replace_wide_character_under_cursor() {
+ let mut vte_parser = vte::Parser::new();
+ let mut grid = Grid::new(21, 104, Palette::default());
+ let fixture_name = "replace_wide_character_under_cursor";
+ let content = read_fixture(fixture_name);
+ for byte in content {
+ vte_parser.advance(&mut grid, byte);
+ }
+ assert_snapshot!(format!("{:?}", grid));
+}
+
+#[test]
+fn wrap_wide_characters() {
+ let mut vte_parser = vte::Parser::new();
+ let mut grid = Grid::new(21, 90, Palette::default());
+ let fixture_name = "wide_characters_full";
+ let content = read_fixture(fixture_name);
+ for byte in content {
+ vte_parser.advance(&mut grid, byte);
+ }
+ assert_snapshot!(format!("{:?}", grid));
+}
+
+#[test]
+fn wrap_wide_characters_on_size_change() {
+ let mut vte_parser = vte::Parser::new();
+ let mut grid = Grid::new(21, 93, Palette::default());
+ let fixture_name = "wide_characters_full";
+ let content = read_fixture(fixture_name);
+ for byte in content {
+ vte_parser.advance(&mut grid, byte);
+ }
+ grid.change_size(21, 90);
+ assert_snapshot!(format!("{:?}", grid));
+}
+
+#[test]
+fn unwrap_wide_characters_on_size_change() {
+ let mut vte_parser = vte::Parser::new();
+ let mut grid = Grid::new(21, 93, Palette::default());
+ let fixture_name = "wide_characters_full";
+ let content = read_fixture(fixture_name);
+ for byte in content {
+ vte_parser.advance(&mut grid, byte);
+ }
+ grid.change_size(21, 90);
+ grid.change_size(21, 93);
+ assert_snapshot!(format!("{:?}", grid));
+}
+
+#[test]
+fn wrap_wide_characters_in_the_middle_of_the_line() {
+ let mut vte_parser = vte::Parser::