diff options
| author | Aram Drevekenin <aram@poor.dev> | 2026-02-05 14:58:45 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-02-05 14:58:45 +0100 |
| commit | c8942cfbb45411c22b978229fbadfe760a98d751 (patch) | |
| tree | f0dd4aa4e8f644c5504091440b94521443a4fec6 | |
| parent | fix: don't trim empty characters with background (#4680) (diff) | |
| download | sl0ppij-c8942cfbb45411c22b978229fbadfe760a98d751.tar.gz sl0ppij-c8942cfbb45411c22b978229fbadfe760a98d751.tar.xz sl0ppij-c8942cfbb45411c22b978229fbadfe760a98d751.zip | |
fix: scrolling with double/triple-click selection (#4681)
* fix: set double/triple-click selection inactive after its end
* add pr
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | zellij-server/src/panes/grid.rs | 6 | ||||
| -rw-r--r-- | zellij-server/src/panes/selection.rs | 4 | ||||
| -rw-r--r-- | zellij-server/src/panes/unit/grid_tests.rs | 143 | ||||
| -rw-r--r-- | zellij-server/src/panes/unit/selection_tests.rs | 77 |
5 files changed, 231 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index bf5d3d77..af82125a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * fix: proper error handling in route (https://github.com/zellij-org/zellij/pull/4655) * feat: allow resizing panes with the mouse (https://github.com/zellij-org/zellij/pull/4678) * fix: don't trim empty colored cells when resizing terminal grid (https://github.com/zellij-org/zellij/pull/4680) +* fix: prevent selection from scrolling up after double/triple-click (https://github.com/zellij-org/zellij/pull/4681) ## [0.43.1] - 2025-08-08 diff --git a/zellij-server/src/panes/grid.rs b/zellij-server/src/panes/grid.rs index ee88e40c..8a7c380e 100644 --- a/zellij-server/src/panes/grid.rs +++ b/zellij-server/src/panes/grid.rs @@ -1850,6 +1850,12 @@ impl Grid { let old_selection = self.selection; self.selection.end(*end); self.update_selected_lines(&old_selection, &self.selection.clone()); + } else { + // we do this rather than using .end() so that the selection will be marked as inactive + // (so we won't keep changing its start/end points as we scroll) but so we won't update + // its end position to the above "end" position (which is incorrect behavior for + // double/triple click - it will mean we won't mark until the end of the word/line) + self.selection.finalize(); } self.mark_for_rerender(); } diff --git a/zellij-server/src/panes/selection.rs b/zellij-server/src/panes/selection.rs index 343ebfde..22c10e18 100644 --- a/zellij-server/src/panes/selection.rs +++ b/zellij-server/src/panes/selection.rs @@ -41,6 +41,10 @@ impl Selection { self.end = end; } + pub fn finalize(&mut self) { + self.active = false; + } + pub fn set_start_and_end_positions(&mut self, start: Position, end: Position) { self.active = true; self.start = start; diff --git a/zellij-server/src/panes/unit/grid_tests.rs b/zellij-server/src/panes/unit/grid_tests.rs index c584e021..80b413f1 100644 --- a/zellij-server/src/panes/unit/grid_tests.rs +++ b/zellij-server/src/panes/unit/grid_tests.rs @@ -4219,3 +4219,146 @@ fn preserve_background_color_on_resize() { trailing_spaces ); } + +fn create_grid_with_content(content: &str) -> Grid { + 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 mut grid = Grid::new( + 20, + 80, + 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(), + false, + true, + true, + true, + false, + ); + for byte in content.as_bytes() { + vte_parser.advance(&mut grid, *byte); + } + grid +} + +#[test] +fn double_click_selection_preserved_after_scroll() { + let content = "line 0\nline 1\nline 2\nline 3\nline 4\nthis is a word test\nline 6\nline 7\nline 8\nline 9\nline 10\nline 11\nline 12\nline 13\nline 14\nline 15\nline 16\nline 17\nline 18\nline 19\n"; + let mut grid = create_grid_with_content(content); + + for _ in 0..20 { + grid.add_canonical_line(); + } + + let word_position = Position::new(5, 10); + grid.start_selection(&word_position); + + let selection_before = grid.get_selected_text(); + let word_start = grid.selection.start; + let word_end = grid.selection.end; + + grid.end_selection(&word_position); + + grid.scroll_up_one_line(); + + let selection_after_start = grid.selection.start; + let selection_after_end = grid.selection.end; + + assert_eq!(selection_after_start.line.0, word_start.line.0 + 1); + assert_eq!(selection_after_end.line.0, word_end.line.0 + 1); + assert_eq!(selection_after_start.column, word_start.column); + assert_eq!(selection_after_end.column, word_end.column); + + let text_after = grid.get_selected_text(); + assert_eq!(selection_before, text_after); +} + +#[test] +fn triple_click_selection_preserved_after_scroll() { + let content = "line 0\nline 1\nline 2\nline 3\nline 4\nthis is line five with some text\nline 6\nline 7\nline 8\nline 9\nline 10\nline 11\nline 12\nline 13\nline 14\nline 15\nline 16\nline 17\nline 18\nline 19\n"; + let mut grid = create_grid_with_content(content); + + for _ in 0..20 { + grid.add_canonical_line(); + } + + let line_position = Position::new(5, 15); + grid.start_selection(&line_position); + grid.start_selection(&line_position); + grid.start_selection(&line_position); + + let selection_before = grid.get_selected_text(); + let line_start = grid.selection.start; + let line_end = grid.selection.end; + + grid.end_selection(&line_position); + + grid.scroll_up_one_line(); + + let selection_after_start = grid.selection.start; + let selection_after_end = grid.selection.end; + + assert_eq!(selection_after_start.line.0, line_start.line.0 + 1); + assert_eq!(selection_after_end.line.0, line_end.line.0 + 1); + assert_eq!(selection_after_start.column, line_start.column); + assert_eq!(selection_after_end.column, line_end.column); + + let text_after = grid.get_selected_text(); + assert_eq!(selection_before, text_after); +} + +#[test] +fn double_click_selection_moves_with_multiple_scrolls() { + let content = "line 0\nline 1\nline 2\nline 3\nline 4\nthis is a word test\nline 6\nline 7\nline 8\nline 9\nline 10\nline 11\nline 12\nline 13\nline 14\nline 15\nline 16\nline 17\nline 18\nline 19\n"; + let mut grid = create_grid_with_content(content); + + for _ in 0..20 { + grid.add_canonical_line(); + } + + let word_position = Position::new(5, 10); + grid.start_selection(&word_position); + + let initial_start = grid.selection.start; + let initial_end = grid.selection.end; + + grid.end_selection(&word_position); + + for _ in 0..5 { + grid.scroll_up_one_line(); + } + + assert_eq!(grid.selection.start.line.0, initial_start.line.0 + 5); + assert_eq!(grid.selection.end.line.0, initial_end.line.0 + 5); + assert_eq!(grid.selection.start.column, initial_start.column); + assert_eq!(grid.selection.end.column, initial_end.column); +} + +#[test] +fn single_click_drag_selection_preserved_after_scroll() { + let content = "line 0\nline 1\nline 2\nline 3\nline 4\nsome text here\nline 6\nline 7\nline 8\nline 9\nline 10\nline 11\nline 12\nline 13\nline 14\nline 15\nline 16\nline 17\nline 18\nline 19\n"; + let mut grid = create_grid_with_content(content); + + for _ in 0..20 { + grid.add_canonical_line(); + } + + grid.start_selection(&Position::new(5, 5)); + grid.update_selection(&Position::new(5, 10)); + + let start_before = grid.selection.start; + let end_before = grid.selection.end; + + grid.end_selection(&Position::new(5, 10)); + + grid.scroll_up_one_line(); + + assert_eq!(grid.selection.start.line.0, start_before.line.0 + 1); + assert_eq!(grid.selection.end.line.0, end_before.line.0 + 1); + assert_eq!(grid.selection.start.column, start_before.column); + assert_eq!(grid.selection.end.column, end_before.column); +} diff --git a/zellij-server/src/panes/unit/selection_tests.rs b/zellij-server/src/panes/unit/selection_tests.rs index 778172f2..4a0b346f 100644 --- a/zellij-server/src/panes/unit/selection_tests.rs +++ b/zellij-server/src/panes/unit/selection_tests.rs @@ -454,3 +454,80 @@ fn add_line_to_position_reduce_from_above() { assert_eq!(selection.start, Position::new(line_index_to_add as i32, 0)); assert_eq!(selection.end, selection_end); } + +#[test] +fn finalize_sets_active_to_false() { + let mut selection = Selection::default(); + selection.start(Position::new(10, 10)); + assert!(selection.active); + + selection.finalize(); + assert!(!selection.active); +} + +#[test] +fn finalize_preserves_positions() { + let start = Position::new(5, 10); + let end = Position::new(15, 20); + let mut selection = Selection { + start, + end, + active: true, + last_added_word_position: Some((start, end)), + last_added_line_index: None, + }; + + selection.finalize(); + + assert_eq!(selection.start, start); + assert_eq!(selection.end, end); + assert!(!selection.active); +} + +#[test] +fn double_click_then_scroll_moves_both_positions() { + let word_start = Position::new(10, 5); + let word_end = Position::new(10, 15); + let mut selection = Selection::default(); + + selection.set_start_and_end_positions(word_start, word_end); + assert!(selection.active); + + selection.finalize(); + assert!(!selection.active); + + selection.move_down(3); + + assert_eq!(selection.start, Position::new(13, 5)); + assert_eq!(selection.end, Position::new(13, 15)); +} + +#[test] +fn triple_click_then_scroll_moves_both_positions() { + let line_start = Position::new(10, 0); + let line_end = Position::new(10, 80); + let mut selection = Selection::default(); + + selection.set_start_and_end_positions(line_start, line_end); + selection.finalize(); + + selection.move_up(5); + + assert_eq!(selection.start, Position::new(5, 0)); + assert_eq!(selection.end, Position::new(5, 80)); +} + +#[test] +fn set_start_and_end_without_finalize_only_moves_start() { + let word_start = Position::new(10, 5); + let word_end = Position::new(10, 15); + let mut selection = Selection::default(); + + selection.set_start_and_end_positions(word_start, word_end); + assert!(selection.active); + + selection.move_down(3); + + assert_eq!(selection.start, Position::new(13, 5)); + assert_eq!(selection.end, word_end); +} |
