Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix some real/saved cursor bugs during resize #3032

Merged
merged 19 commits into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
2ed84e3
refactor: Simplify transfer_rows_from_viewport_to_lines_above
aidanhs Dec 17, 2023
c1d8b5d
perf: Batch remove rows from the viewport for performance
aidanhs Dec 17, 2023
78fafb1
perf: Optimize Row::drain_until by splitting chars in one step
aidanhs Dec 18, 2023
cca9656
refactor: Simplify `if let` into a `.map`
aidanhs Dec 20, 2023
8ce6ce1
refactor: There are only new saved coordinates when there were old ones
aidanhs Dec 24, 2023
110bf46
refactor: Unify viewport transfer: use common variable names
aidanhs Dec 24, 2023
75143e6
fix: Use same saved cursor logic in height resize as width
aidanhs Dec 25, 2023
37d0dab
fix: Correct saved+real cursor calculations when reflowing long lines
aidanhs Dec 25, 2023
918b642
fix: Don't create canonical lines if cursor ends on EOL after resize
aidanhs Dec 25, 2023
e2c9d6f
fix: for cursor index calculation in lines that are already wrapped
aidanhs Dec 25, 2023
fba2574
chore: test for real/saved cursor position being handled separately
aidanhs Dec 26, 2023
528ccbf
chore: Apply cargo format
aidanhs Dec 27, 2023
11b59da
chore(repo): update issue templates
imsnif Dec 28, 2023
956b50d
Bump rust version to 1.75.0 (#3039)
har7an Jan 8, 2024
5660f72
feat(plugins): introduce 'pipes', allowing users to pipe data to and …
imsnif Jan 17, 2024
3847732
docs(changelog): introduce pipes
imsnif Jan 17, 2024
6a68400
fix: add some robustness and future proofing
imsnif Jan 18, 2024
3870ac8
fix e2e tests
imsnif Jan 22, 2024
ca23236
Merge branch 'main' into aphs-improve-reflow-perf
imsnif Jan 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
---
source: src/tests/e2e/cases.rs
assertion_line: 1194
assertion_line: 1326
expression: last_snapshot
---
Zellij (e2e-test)  Tab #1
$$
$
Expand Down
126 changes: 85 additions & 41 deletions zellij-server/src/panes/grid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -623,8 +623,8 @@ impl Grid {
cursor_canonical_line_index = i;
}
if i == self.cursor.y {
let line_wrap_position_in_line = self.cursor.y - cursor_canonical_line_index;
cursor_index_in_canonical_line = line_wrap_position_in_line + self.cursor.x;
let line_wraps = self.cursor.y.saturating_sub(cursor_canonical_line_index);
cursor_index_in_canonical_line = (line_wraps * self.width) + self.cursor.x;
break;
}
}
Expand All @@ -639,10 +639,9 @@ impl Grid {
cursor_canonical_line_index = i;
}
if i == saved_cursor_position.y {
let line_wrap_position_in_line =
saved_cursor_position.y - cursor_canonical_line_index;
let line_wraps = saved_cursor_position.y - cursor_canonical_line_index;
cursor_index_in_canonical_line =
line_wrap_position_in_line + saved_cursor_position.x;
(line_wraps * self.width) + saved_cursor_position.x;
break;
}
}
Expand Down Expand Up @@ -849,26 +848,44 @@ impl Grid {

self.viewport = new_viewport_rows;

let mut new_cursor_y = self.canonical_line_y_coordinates(cursor_canonical_line_index);
let mut new_cursor_y = self.canonical_line_y_coordinates(cursor_canonical_line_index)
+ (cursor_index_in_canonical_line / new_columns);
let mut saved_cursor_y_coordinates =
if let Some(saved_cursor) = self.saved_cursor_position.as_ref() {
Some(self.canonical_line_y_coordinates(saved_cursor.y))
} else {
None
};

let new_cursor_x = (cursor_index_in_canonical_line / new_columns)
+ (cursor_index_in_canonical_line % new_columns);
let saved_cursor_x_coordinates = if let Some(saved_cursor_index_in_canonical_line) =
saved_cursor_index_in_canonical_line.as_ref()
{
Some(
(*saved_cursor_index_in_canonical_line / new_columns)
+ (*saved_cursor_index_in_canonical_line % new_columns),
)
} else {
None
self.saved_cursor_position.as_ref().map(|saved_cursor| {
self.canonical_line_y_coordinates(saved_cursor.y)
+ saved_cursor_index_in_canonical_line.as_ref().unwrap() / new_columns
});

// A cursor at EOL has two equivalent positions - end of this line or beginning of
// next. If not already at the beginning of line, bias to EOL so add character logic
// doesn't create spurious canonical lines
let mut new_cursor_x = cursor_index_in_canonical_line % new_columns;
if self.cursor.x != 0 && new_cursor_x == 0 {
new_cursor_y = new_cursor_y.saturating_sub(1);
new_cursor_x = new_columns
}
let saved_cursor_x_coordinates = match (
saved_cursor_index_in_canonical_line.as_ref(),
self.saved_cursor_position.as_mut(),
saved_cursor_y_coordinates.as_mut(),
) {
(
Some(saved_cursor_index_in_canonical_line),
Some(saved_cursor_position),
Some(saved_cursor_y_coordinates),
) => {
let x = saved_cursor_position.x;
let mut new_x = *saved_cursor_index_in_canonical_line % new_columns;
let new_y = saved_cursor_y_coordinates;
if x != 0 && new_x == 0 {
*new_y = new_y.saturating_sub(1);
new_x = new_columns
}
Some(new_x)
},
_ => None,
};

let current_viewport_row_count = self.viewport.len();
match current_viewport_row_count.cmp(&self.height) {
Ordering::Less => {
Expand Down Expand Up @@ -919,14 +936,27 @@ impl Grid {
saved_cursor_position.x = saved_cursor_x_coordinates;
saved_cursor_position.y = saved_cursor_y_coordinates;
},
_ => {
saved_cursor_position.x = new_cursor_x;
saved_cursor_position.y = new_cursor_y;
},
_ => 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
.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);

let current_viewport_row_count = self.viewport.len();
match current_viewport_row_count.cmp(&new_rows) {
Ordering::Less => {
Expand All @@ -939,25 +969,24 @@ impl Grid {
new_columns,
);
let rows_pulled = self.viewport.len() - current_viewport_row_count;
self.cursor.y += rows_pulled;
if let Some(saved_cursor_position) = self.saved_cursor_position.as_mut() {
saved_cursor_position.y += rows_pulled
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 - new_rows;
if row_count_to_transfer > self.cursor.y {
self.cursor.y = 0;
if let Some(saved_cursor_position) = self.saved_cursor_position.as_mut() {
saved_cursor_position.y = 0
};
if row_count_to_transfer > new_cursor_y {
new_cursor_y = 0;
} else {
self.cursor.y -= row_count_to_transfer;
if let Some(saved_cursor_position) = self.saved_cursor_position.as_mut() {
saved_cursor_position.y = saved_cursor_position
.y
.saturating_sub(row_count_to_transfer);
};
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,
Expand All @@ -969,6 +998,21 @@ impl Grid {
},
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
),
}
};
}
self.height = new_rows;
self.width = new_columns;
Expand Down
121 changes: 121 additions & 0 deletions zellij-server/src/panes/unit/grid_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2400,6 +2400,127 @@ pub fn scroll_up_increase_width_and_scroll_down() {
assert_snapshot!(format!("{:?}", grid));
}

#[test]
fn saved_cursor_across_resize() {
let mut vte_parser = vte::Parser::new();
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
let debug = false;
let arrow_fonts = true;
let styled_underlines = true;
let mut grid = Grid::new(
4,
20,
Rc::new(RefCell::new(Palette::default())),
terminal_emulator_color_codes,
Rc::new(RefCell::new(LinkHandler::new())),
Rc::new(RefCell::new(None)),
sixel_image_store,
Style::default(),
debug,
arrow_fonts,
styled_underlines,
);
let mut parse = |s, grid: &mut Grid| {
for b in Vec::from(s) {
vte_parser.advance(&mut *grid, b)
}
};
let content = "
\rLine 1 >fill to 20_<
\rLine 2 >fill to 20_<
\rLine 3 >fill to 20_<
\rL\u{1b}[sine 4 >fill to 20_<";
parse(content, &mut grid);
// Move real cursor position up three lines
let content = "\u{1b}[3A";
parse(content, &mut grid);
// Truncate top of terminal, resetting cursor (but not saved cursor)
grid.change_size(3, 20);
// Wrap, resetting cursor again (but not saved cursor)
grid.change_size(3, 10);
// Restore saved cursor position and write ZZZ
let content = "\u{1b}[uZZZ";
parse(content, &mut grid);
assert_snapshot!(format!("{:?}", grid));
}

#[test]
fn saved_cursor_across_resize_longline() {
let mut vte_parser = vte::Parser::new();
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
let debug = false;
let arrow_fonts = true;
let styled_underlines = true;
let mut grid = Grid::new(
4,
20,
Rc::new(RefCell::new(Palette::default())),
terminal_emulator_color_codes,
Rc::new(RefCell::new(LinkHandler::new())),
Rc::new(RefCell::new(None)),
sixel_image_store,
Style::default(),
debug,
arrow_fonts,
styled_underlines,
);
let mut parse = |s, grid: &mut Grid| {
for b in Vec::from(s) {
vte_parser.advance(&mut *grid, b)
}
};
let content = "
\rLine 1 >fill \u{1b}[sto 20_<";
parse(content, &mut grid);
// Wrap each line precisely halfway
grid.change_size(4, 10);
// Write 'YY' at the end (ends up on a new wrapped line), restore to the saved cursor
// and overwrite 'to' with 'ZZ'
let content = "YY\u{1b}[uZZ";
parse(content, &mut grid);
assert_snapshot!(format!("{:?}", grid));
}

#[test]
fn saved_cursor_across_resize_rewrap() {
let mut vte_parser = vte::Parser::new();
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
let debug = false;
let arrow_fonts = true;
let styled_underlines = true;
let mut grid = Grid::new(
4,
4 * 8,
Rc::new(RefCell::new(Palette::default())),
terminal_emulator_color_codes,
Rc::new(RefCell::new(LinkHandler::new())),
Rc::new(RefCell::new(None)),
sixel_image_store,
Style::default(),
debug,
arrow_fonts,
styled_underlines,
);
let mut parse = |s, grid: &mut Grid| {
for b in Vec::from(s) {
vte_parser.advance(&mut *grid, b)
}
};
let content = "
\r12345678123456781234567\u{1b}[s812345678"; // 4*8 chars
parse(content, &mut grid);
// Wrap each line precisely halfway, then rewrap to halve them again
grid.change_size(4, 16);
grid.change_size(4, 8);
// Write 'Z' at the end of line 3
let content = "\u{1b}[uZ";
parse(content, &mut grid);
assert_snapshot!(format!("{:?}", grid));
}

#[test]
pub fn move_cursor_below_scroll_region() {
let mut vte_parser = vte::Parser::new();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
source: zellij-server/src/panes/./unit/grid_tests.rs
assertion_line: 2443
expression: "format!(\"{:?}\", grid)"
---
00 (W): ll to 20_<
01 (C): LZZZ 4 >fi
02 (W): ll to 20_<

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
source: zellij-server/src/panes/./unit/grid_tests.rs
assertion_line: 2475
expression: "format!(\"{:?}\", grid)"
---
00 (C):
01 (C): Line 1 >fi
02 (W): ll ZZ 20_<
03 (W): YY

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
source: zellij-server/src/panes/./unit/grid_tests.rs
assertion_line: 2512
expression: "format!(\"{:?}\", grid)"
---
00 (C): 12345678
01 (W): 12345678
02 (W): 1234567Z
03 (W): 12345678

12 changes: 11 additions & 1 deletion zellij-server/src/tab/unit/tab_integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2039,10 +2039,20 @@ fn save_cursor_position_across_resizes() {
1,
Vec::from("\n\n\rI am some text\n\rI am another line of text\n\rLet's save the cursor position here \u{1b}[sI should be ovewritten".as_bytes()),
).unwrap();
tab.resize_whole_tab(Size { cols: 100, rows: 3 }).unwrap();

// We check cursor and saved cursor are handled separately by:
// 1. moving real cursor up two lines
tab.handle_pty_bytes(1, Vec::from("\u{1b}[2A".as_bytes()));
// 2. resizing so real cursor gets lost above the viewport, which resets it to row 0
// The saved cursor ends up on row 1, allowing detection if it (incorrectly) gets reset too
tab.resize_whole_tab(Size { cols: 35, rows: 4 }).unwrap();

// Now overwrite
tab.handle_pty_bytes(1, Vec::from("\u{1b}[uthis overwrote me!".as_bytes()))
.unwrap();

tab.resize_whole_tab(Size { cols: 100, rows: 3 }).unwrap();

tab.render(&mut output).unwrap();
let snapshot = take_snapshot(
output.serialize().unwrap().get(&client_id).unwrap(),
Expand Down