Skip to content

Commit

Permalink
fix(compatibility): replace wide characters under cursor properly (#1196
Browse files Browse the repository at this point in the history
)

* fix(compatibility): replace wide characters under cursor properly

* style(fmt): rustfmt
  • Loading branch information
imsnif authored Mar 9, 2022
1 parent ed33164 commit 95e512c
Show file tree
Hide file tree
Showing 11 changed files with 212 additions and 22 deletions.
5 changes: 5 additions & 0 deletions src/tests/fixtures/ncmpcpp-wide-chars

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/tests/fixtures/replace_multiple_wide_characters
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
👨🔭👨🔭xy
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
👨x🔭🔭👨
2 changes: 1 addition & 1 deletion src/tests/fixtures/vttest3-0
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@
12. Modify test-parameters

Enter choice number (0 - 12): 3
[?42l(B)B*B+BSelected as G0 (with SI)Selected as G1 (with SO))B(BCharacter set B (US ASCII)(B)B !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~)B(B !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~)B(BCharacter set A (British)(A)B !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~)A(B !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~)B(BCharacter set 0 (DEC Special graphics and line drawing)(0)B !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~)0(B !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~)B(BCharacter set 1 (DEC Alternate character ROM standard characters)(1)B !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~)1(B !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~)B(BCharacter set 2 (DEC Alternate character ROM special graphics)(2)B !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~)2(B !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~(B)BThese are the installed character sets. Push <RETURN>
[?42l(B)B*B+BSelected as G0 (with SI)Selected as G1 (with SO))B(BCharacter set B (US ASCII)(B)B !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~)B(B !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~)B(BCharacter set A (British)(A)B !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~)A(B !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~)B(BCharacter set 0 (DEC Special graphics and line drawing)(0)B !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~)0(B !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~)B(BCharacter set 1 (DEC Alternate character ROM standard characters)(1)B !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~)1(B !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~)B(BCharacter set 2 (DEC Alternate character ROM special graphics)(2)B !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~)2(B !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~(B)BThese are the installed character sets. Push <RETURN>
Expand Down
69 changes: 53 additions & 16 deletions zellij-server/src/panes/grid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,7 @@ impl Grid {
row.insert_character_at(terminal_character, self.cursor.x);
} else {
row.add_character_at(terminal_character, self.cursor.x);
// self.move_cursor_forward_until_edge(count_to_move_cursor);
}
self.output_buffer.update_line(self.cursor.y);
}
Expand All @@ -967,6 +968,9 @@ 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 character_width == 0 {
return;
}
if self.cursor.x + character_width > self.width {
if self.disable_linewrap {
return;
Expand Down Expand Up @@ -1060,7 +1064,7 @@ impl Grid {

fn pad_current_line_until(&mut self, position: usize) {
let current_row = self.viewport.get_mut(self.cursor.y).unwrap();
for _ in current_row.len()..position {
for _ in current_row.width()..position {
current_row.push(EMPTY_TERMINAL_CHARACTER);
}
self.output_buffer.update_line(self.cursor.y);
Expand Down Expand Up @@ -2175,40 +2179,73 @@ impl Row {
}
absolute_index
}
pub fn absolute_character_index_and_position_in_char(&self, x: usize) -> (usize, usize) {
// returns x's width aware index as well as its position inside the wide char (eg. 1 if
// it's in the middle of a 2-char wide character)
let mut accumulated_width = 0;
let mut absolute_index = x;
let mut position_inside_character = 0;
for (i, terminal_character) in self.columns.iter().enumerate() {
accumulated_width += terminal_character.width;
absolute_index = i;
if accumulated_width > x {
let character_start_position = accumulated_width - terminal_character.width;
position_inside_character = x - character_start_position;
break;
}
}
(absolute_index, position_inside_character)
}
pub fn add_character_at(&mut self, terminal_character: TerminalCharacter, x: usize) {
match self.width_cached().cmp(&x) {
Ordering::Equal => {
// adding the character at the end of the current line
self.columns.push_back(terminal_character);
// this is unwrapped because this always happens after self.width_cached()
*self.width.as_mut().unwrap() += terminal_character.width;
}
Ordering::Less => {
// adding the character after the end of the current line
// we pad the line up to the character and then add it
let width_offset = self.excess_width_until(x);
self.columns
.resize(x.saturating_sub(width_offset), EMPTY_TERMINAL_CHARACTER);
self.columns.push_back(terminal_character);
self.width = None;
}
Ordering::Greater => {
// wide-character-aware index, where each character is counted once
let absolute_x_index = self.absolute_character_index(x);
// adding the character in the middle of the line
// we replace the character at its position
let (absolute_x_index, position_inside_character) =
self.absolute_character_index_and_position_in_char(x);
let character_width = terminal_character.width;
let replaced_character =
std::mem::replace(&mut self.columns[absolute_x_index], terminal_character);
match character_width.cmp(&replaced_character.width) {
Ordering::Greater => {
// this is done in a verbose manner because of performance
let width_difference = character_width - replaced_character.width;
for _ in 0..width_difference {
let position_to_remove = absolute_x_index + 1;
if self.columns.get(position_to_remove).is_some() {
self.columns.remove(position_to_remove);
// the replaced character is narrower than the current character
// (eg. we added a wide emoji in place of an English character)
// we remove the character after it to make room
let position_to_remove = absolute_x_index + 1;
if let Some(removed) = self.columns.remove(position_to_remove) {
if removed.width > 1 {
// the character we removed is a wide character itself, so we add
// padding
self.columns
.insert(position_to_remove, EMPTY_TERMINAL_CHARACTER);
}
}
}
Ordering::Less => {
let width_difference = replaced_character.width - character_width;
for _ in 0..width_difference {
// the replaced character is wider than the current character
// (eg. we added an English character in place of a wide emoji)
// we must make sure to add padding either before the character we added
// or after it, depending on our position inside said removed wide character
// TODO: support characters wider than 2
if position_inside_character > 0 {
self.columns
.insert(absolute_x_index, EMPTY_TERMINAL_CHARACTER);
} else {
self.columns
.insert(absolute_x_index + 1, EMPTY_TERMINAL_CHARACTER);
}
Expand All @@ -2235,14 +2272,14 @@ impl Row {
self.width = None;
}
pub fn replace_character_at(&mut self, terminal_character: TerminalCharacter, x: usize) {
// this is much more performant than remove/insert
if x < self.columns.len() {
let absolute_x_index = self.absolute_character_index(x);
if absolute_x_index < self.columns.len() {
self.columns.push_back(terminal_character);
// let character = self.columns.swap_remove_back(x);
let character = self.columns.swap_remove_back(x).unwrap(); // TODO: if let?
// this is much more performant than remove/insert
let character = self.columns.swap_remove_back(absolute_x_index).unwrap();
let excess_width = character.width.saturating_sub(1);
for _ in 0..excess_width {
self.columns.insert(x, terminal_character);
self.columns.insert(absolute_x_index, terminal_character);
}
}
self.width = None;
Expand Down
42 changes: 42 additions & 0 deletions zellij-server/src/panes/unit/grid_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1560,3 +1560,45 @@ pub fn fzf_fullscreen() {
}
assert_snapshot!(format!("{:?}", grid));
}

#[test]
pub fn replace_multiple_wide_characters_under_cursor() {
// this test makes sure that if we replace a wide character with a non-wide character, it
// properly pads the excess width in the proper place (either before the replaced non-wide
// character if the cursor was "in the middle" of the wide character, or after the character if
// it was "in the beginning" of the wide character)
let mut vte_parser = vte::Parser::new();
let mut grid = Grid::new(
51,
112,
Palette::default(),
Rc::new(RefCell::new(LinkHandler::new())),
);
let fixture_name = "replace_multiple_wide_characters";
let content = read_fixture(fixture_name);
for byte in content {
vte_parser.advance(&mut grid, byte);
}
assert_snapshot!(format!("{:?}", grid));
}

#[test]
pub fn replace_non_wide_characters_with_wide_characters() {
// this test makes sure that if we replace a wide character with a non-wide character, it
// properly pads the excess width in the proper place (either before the replaced non-wide
// character if the cursor was "in the middle" of the wide character, or after the character if
// it was "in the beginning" of the wide character)
let mut vte_parser = vte::Parser::new();
let mut grid = Grid::new(
51,
112,
Palette::default(),
Rc::new(RefCell::new(LinkHandler::new())),
);
let fixture_name = "replace_non_wide_characters_with_wide_characters";
let content = read_fixture(fixture_name);
for byte in content {
vte_parser.advance(&mut grid, byte);
}
assert_snapshot!(format!("{:?}", grid));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
source: zellij-server/src/panes/./unit/grid_tests.rs
expression: "format!(\"{:?}\", grid)"

---
00 (C): 👨y x🔭
01 (C):

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
source: zellij-server/src/panes/./unit/grid_tests.rs
expression: "format!(\"{:?}\", grid)"

---
00 (C): 👨👨 🔭
01 (C):

Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,23 @@ expression: "format!(\"{:?}\", grid)"
02 (C): Character set B (US ASCII)
03 (C): !"#$%&'()*+,-./0123456789:;<=>? !"#$%&'()*+,-./0123456789:;<=>?
04 (C): @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
05 (C): `abcdefghijklmnopqrstuvwxyz{|}~ `abcdefghijklmnopqrstuvwxyz{|}~
05 (C): `abcdefghijklmnopqrstuvwxyz{|}~ `abcdefghijklmnopqrstuvwxyz{|}~
06 (C): Character set A (British)
07 (C): !"#$%&'()*+,-./0123456789:;<=>? !"#$%&'()*+,-./0123456789:;<=>?
08 (C): @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
09 (C): `abcdefghijklmnopqrstuvwxyz{|}~ `abcdefghijklmnopqrstuvwxyz{|}~
09 (C): `abcdefghijklmnopqrstuvwxyz{|}~ `abcdefghijklmnopqrstuvwxyz{|}~
10 (C): Character set 0 (DEC Special graphics and line drawing)
11 (C): !"#$%&'()*+,-./0123456789:;<=>? !"#$%&'()*+,-./0123456789:;<=>?
12 (C): @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
13 (C): ◆▒␉␌␍␊°±␤␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥π≠£· ◆▒␉␌␍␊°±␤␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥π≠£·
13 (C): ◆▒␉␌␍␊°±␤␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥π≠£· ◆▒␉␌␍␊°±␤␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥π≠£·
14 (C): Character set 1 (DEC Alternate character ROM standard characters)
15 (C): !"#$%&'()*+,-./0123456789:;<=>? !"#$%&'()*+,-./0123456789:;<=>?
16 (C): @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
17 (C): `abcdefghijklmnopqrstuvwxyz{|}~ `abcdefghijklmnopqrstuvwxyz{|}~
17 (C): `abcdefghijklmnopqrstuvwxyz{|}~ `abcdefghijklmnopqrstuvwxyz{|}~
18 (C): Character set 2 (DEC Alternate character ROM special graphics)
19 (C): !"#$%&'()*+,-./0123456789:;<=>? !"#$%&'()*+,-./0123456789:;<=>?
20 (C): @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
21 (C): `abcdefghijklmnopqrstuvwxyz{|}~ `abcdefghijklmnopqrstuvwxyz{|}~
21 (C): `abcdefghijklmnopqrstuvwxyz{|}~ `abcdefghijklmnopqrstuvwxyz{|}~
22 (C):
23 (C): These are the installed character sets. Push <RETURN>
24 (C):
Expand Down
Loading

0 comments on commit 95e512c

Please sign in to comment.