diff --git a/docs/changelog.rst b/docs/changelog.rst index fc9477c75..8c5264c1d 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -217,7 +217,7 @@ Detailed list of changes - Fix dragging of splits layout borders sometimes moving in the wrong direction or having no effect (:pull:`9447`) -- Fix triple-click line selection not extending wrapped lines beyond the bottom edge of the viewport +- Fix triple-click line selection and double-click word selection not extending wrapped lines beyond the edges of the viewport - Password input in kittens: hide the cursor and display a blinking 🔒 at the end of typed characters to make it visually clear the user is entering a password diff --git a/kitty/screen.c b/kitty/screen.c index b83a6a1e8..74cb532d2 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -5406,6 +5406,36 @@ continue_line_downwards_offscreen(Screen *self, int bottom_line, SelectionBounda return num_offscreen; } +static index_type +continue_word_upwards_scrollback(Screen *self, int range_y, index_type *start_x) { + index_type num_in_scrollback = 0; + Line *line = NULL; + while (*start_x == 0 && range_line_is_continued(self, range_y) && (line = checked_range_line(self, range_y - 1))) { + if (!is_char_ok_for_word_extension(line, self->columns - 1, false)) break; + range_y--; + num_in_scrollback++; + index_type s = self->columns - 1; + while (s > 0 && is_char_ok_for_word_extension(line, s - 1, false)) s--; + *start_x = s; + } + return num_in_scrollback; +} + +static index_type +continue_word_downwards_offscreen(Screen *self, int range_y, index_type *end_x) { + index_type num_offscreen = 0; + Line *line = NULL; + while (*end_x >= self->columns - 1 && (line = checked_range_line(self, range_y + 1)) && range_line_is_continued(self, range_y + 1)) { + if (!is_char_ok_for_word_extension(line, 0, true)) break; + range_y++; + num_offscreen++; + index_type e = 0; + while (e < self->columns - 1 && is_char_ok_for_word_extension(line, e + 1, true)) e++; + *end_x = e; + } + return num_offscreen; +} + static int clamp_selection_input_to_multicell(Screen *self, const Selection *s, index_type x, index_type y, bool in_left_half_of_cell) { int delta = 0; @@ -5498,6 +5528,25 @@ do_update_selection(Screen *self, Selection *s, index_type x, index_type y, bool } if (s->adjusting_start || adjust_both_ends) s->start_scrolled_by = self->scrolled_by; if (!s->adjusting_start || adjust_both_ends) s->end_scrolled_by = self->scrolled_by; + // extend word into scrollback if needed + if (start.y == 0 && self->linebuf == self->main_linebuf && + (adjust_both_ends || adjusted_boundary_is_before)) { + index_type num_in_scrollback = continue_word_upwards_scrollback(self, 0, &start.x); + if (num_in_scrollback) { + s->start_scrolled_by += num_in_scrollback; + s->start.x = start.x; + } + } + // extend word below viewport if needed + if (end.y >= self->lines - 1 && self->scrolled_by > 0 && self->linebuf == self->main_linebuf && + (adjust_both_ends || !adjusted_boundary_is_before)) { + int range_bottom = (int)end.y - (int)self->scrolled_by; + index_type num_below_viewport = continue_word_downwards_offscreen(self, range_bottom, &end.x); + if (num_below_viewport) { + s->end_scrolled_by -= num_below_viewport; + s->end.x = end.x; + } + } } else { *a = s->input_current; if (s->adjusting_start) s->start_scrolled_by = self->scrolled_by; else s->end_scrolled_by = self->scrolled_by; diff --git a/kitty_tests/mouse.py b/kitty_tests/mouse.py index d9f270e10..efd6561b2 100644 --- a/kitty_tests/mouse.py +++ b/kitty_tests/mouse.py @@ -282,6 +282,25 @@ class TestMouse(BaseTest): self.ae(sel(), '678\nABCDE12345') s.scroll(100, False) + # word select for wrapped words in scrollback + s.reset() + s.draw('abcde12345') + s.linefeed(), s.carriage_return() + s.draw(('X' * s.columns) * (s.lines-1)) + multi_click(x=1) + self.ae(sel(), 'abcde12345') + + # word select for wrapped words below viewport + s.reset() + s.scroll(100, False) + s.draw(('X' * s.columns) * (s.lines - 1)) + s.linefeed(), s.carriage_return() + s.draw('abcde12345') + s.scroll(1, True) + multi_click(x=1, y=s.lines - 1) + self.ae(sel(), 'abcde12345') + s.scroll(100, False) + # Rectangle select init() press(x=1, y=1, modifiers=GLFW_MOD_ALT | GLFW_MOD_CONTROL)