diff --git a/kitty/mouse.c b/kitty/mouse.c index 1b64df475..b5da8bc67 100644 --- a/kitty/mouse.c +++ b/kitty/mouse.c @@ -107,11 +107,11 @@ cell_for_pos(Window *w, unsigned int *x, unsigned int *y) { #define HANDLER(name) static inline void name(Window UNUSED *w, int UNUSED button, int UNUSED modifiers, unsigned int UNUSED window_idx) static inline void -update_drag(bool from_button, Window *w, bool is_release) { +update_drag(bool from_button, Window *w, bool is_release, int modifiers) { Screen *screen = w->render_data.screen; if (from_button) { if (is_release) screen_update_selection(screen, w->mouse_cell_x, w->mouse_cell_y, true); - else screen_start_selection(screen, w->mouse_cell_x, w->mouse_cell_y); + else screen_start_selection(screen, w->mouse_cell_x, w->mouse_cell_y, modifiers == (GLFW_MOD_CONTROL | GLFW_MOD_ALT)); } else if (screen->selection.in_progress) { screen_update_selection(screen, w->mouse_cell_x, w->mouse_cell_y, false); call_boss(set_primary_selection, NULL); @@ -130,7 +130,7 @@ drag_scroll(Window *w, OSWindow *frame) { Screen *screen = w->render_data.screen; if (screen->linebuf == screen->main_linebuf) { screen_history_scroll(screen, SCROLL_LINE, upwards); - update_drag(false, w, false); + update_drag(false, w, false, 0); frame->last_mouse_activity_at = monotonic(); if (mouse_cursor_shape != ARROW) { mouse_cursor_shape = ARROW; @@ -199,7 +199,7 @@ HANDLER(handle_move_event) { if (screen->selection.in_progress && button == GLFW_MOUSE_BUTTON_LEFT) { double now = monotonic(); if ((now - w->last_drag_scroll_at) >= 0.02 || mouse_cell_changed) { - update_drag(false, w, false); + update_drag(false, w, false, 0); w->last_drag_scroll_at = monotonic(); } } @@ -226,7 +226,7 @@ multi_click(Window *w, unsigned int count) { break; } if (found_selection) { - screen_start_selection(screen, start, w->mouse_cell_y); + screen_start_selection(screen, start, w->mouse_cell_y, false); screen_update_selection(screen, end, w->mouse_cell_y, true); call_boss(set_primary_selection, NULL); } @@ -272,7 +272,7 @@ HANDLER(handle_button_event) { if (handle_in_kitty) { switch(button) { case GLFW_MOUSE_BUTTON_LEFT: - update_drag(true, w, is_release); + update_drag(true, w, is_release, modifiers); if (is_release) { if (modifiers == (int)OPT(open_url_modifiers)) { open_url(w); diff --git a/kitty/screen.c b/kitty/screen.c index b6f6a5c6c..e1ecdcad1 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -1226,15 +1226,36 @@ visual_line_(Screen *self, index_type y) { return self->linebuf->line; } +#define iterate_over_rectangle(start, end) { \ + index_type min_y = MIN(start->y, end->y), max_y = MAX(start->y, end->y); \ + index_type min_x = MIN(start->x, end->x), max_x = MAX(start->x, end->x); \ + for (index_type y = min_y; y <= max_y; y++) { \ + Line *line = visual_line_(self, y); \ + index_type xlimit = xlimit_for_line(line); \ + xlimit = MIN(max_x + 1, xlimit); \ + index_type x_start = min_x; \ + +#define iterate_over_region(start, end) { \ + for (index_type y = start->y; y <= end->y; y++) { \ + Line *line = visual_line_(self, y); \ + index_type xlimit = xlimit_for_line(line); \ + if (y == end->y) xlimit = MIN(end->x + 1, xlimit); \ + index_type x_start = y == start->y ? start->x : 0; + + static inline void -apply_selection(Screen *self, uint8_t *data, SelectionBoundary *start, SelectionBoundary *end, uint8_t set_mask) { +apply_selection(Screen *self, uint8_t *data, SelectionBoundary *start, SelectionBoundary *end, uint8_t set_mask, bool rectangle_select) { if (is_selection_empty(self, start->x, start->y, end->x, end->y)) return; - for (index_type y = start->y; y <= end->y; y++) { - Line *line = visual_line_(self, y); - index_type xlimit = xlimit_for_line(line); - if (y == end->y) xlimit = MIN(end->x + 1, xlimit); - uint8_t *line_start = data + self->columns * y; - for (index_type x = (y == start->y ? start->x : 0); x < xlimit; x++) line_start[x] |= set_mask; + if (rectangle_select) { + iterate_over_rectangle(start, end) + uint8_t *line_start = data + self->columns * y; + for (index_type x = x_start; x < xlimit; x++) line_start[x] |= set_mask; + }} + } else { + iterate_over_region(start, end) + uint8_t *line_start = data + self->columns * y; + for (index_type x = x_start; x < xlimit; x++) line_start[x] |= set_mask; + }} } } @@ -1245,26 +1266,30 @@ screen_apply_selection(Screen *self, void *address, size_t size) { self->last_selection_scrolled_by = self->scrolled_by; self->selection_updated_once = true; selection_limits_(selection, &self->last_rendered_selection_start, &self->last_rendered_selection_end); - apply_selection(self, address, &self->last_rendered_selection_start, &self->last_rendered_selection_end, 1); + apply_selection(self, address, &self->last_rendered_selection_start, &self->last_rendered_selection_end, 1, self->rectangle_select); selection_limits_(url_range, &self->last_rendered_url_start, &self->last_rendered_url_end); - apply_selection(self, address, &self->last_rendered_url_start, &self->last_rendered_url_end, 2); + apply_selection(self, address, &self->last_rendered_url_start, &self->last_rendered_url_end, 2, false); } static inline PyObject* -text_for_range(Screen *self, SelectionBoundary start, SelectionBoundary end) { - Py_ssize_t i = 0, num_of_lines = end.y - start.y + 1; +text_for_range(Screen *self, SelectionBoundary start, SelectionBoundary end, bool rectangle_select, bool insert_newlines) { + int num_of_lines = end.y - start.y + 1, i = 0; PyObject *ans = PyTuple_New(num_of_lines); if (ans == NULL) return PyErr_NoMemory(); - for (index_type y = start.y; y <= end.y; y++, i++) { - Line *line = visual_line_(self, y); - index_type xlimit = xlimit_for_line(line); - if (y == end.y) xlimit = MIN(end.x + 1, xlimit); - index_type xstart = (y == start.y ? start.x : 0); - char leading_char = i > 0 && !line->continued ? '\n' : 0; - PyObject *text = unicode_in_range(line, xstart, xlimit, true, leading_char); - if (text == NULL) { Py_DECREF(ans); return PyErr_NoMemory(); } - PyTuple_SET_ITEM(ans, i, text); - } +#define action \ + char leading_char = (i > 0 && insert_newlines && !line->continued) ? '\n' : 0; \ + PyObject *text = unicode_in_range(line, x_start, xlimit, true, leading_char); \ + if (text == NULL) { Py_DECREF(ans); return PyErr_NoMemory(); } \ + PyTuple_SET_ITEM(ans, i++, text); + + if (rectangle_select) { + iterate_over_rectangle((&start), (&end)) + action + }}} else { + iterate_over_region((&start), (&end)) + action + }}} +#undef action return ans; } @@ -1273,7 +1298,7 @@ screen_open_url(Screen *self) { SelectionBoundary start, end; selection_limits_(url_range, &start, &end); if (is_selection_empty(self, start.x, start.y, end.x, end.y)) return false; - PyObject *text = text_for_range(self, start, end); + PyObject *text = text_for_range(self, start, end, false, false); if (text) { call_boss(open_url_lines, "(O)", text); Py_CLEAR(text); } else PyErr_Print(); return true; @@ -1435,7 +1460,7 @@ text_for_selection(Screen *self) { SelectionBoundary start, end; selection_limits_(selection, &start, &end); if (is_selection_empty(self, start.x, start.y, end.x, end.y)) return PyTuple_New(0); - return text_for_range(self, start, end); + return text_for_range(self, start, end, self->rectangle_select, true); } bool @@ -1524,7 +1549,8 @@ screen_is_selection_dirty(Screen *self) { } void -screen_start_selection(Screen *self, index_type x, index_type y) { +screen_start_selection(Screen *self, index_type x, index_type y, bool rectangle_select) { + self->rectangle_select = rectangle_select; #define A(attr, val) self->selection.attr = val; A(start_x, x); A(end_x, x); A(start_y, y); A(end_y, y); A(start_scrolled_by, self->scrolled_by); A(end_scrolled_by, self->scrolled_by); A(in_progress, true); #undef A diff --git a/kitty/screen.h b/kitty/screen.h index 5ae7605b8..4264f91e1 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -36,7 +36,7 @@ typedef struct { Selection selection; SelectionBoundary last_rendered_selection_start, last_rendered_selection_end, last_rendered_url_start, last_rendered_url_end; Selection url_range; - bool use_latin1, selection_updated_once, is_dirty, scroll_changed; + bool use_latin1, selection_updated_once, is_dirty, scroll_changed, rectangle_select; Cursor *cursor; SavepointBuffer main_savepoints, alt_savepoints; PyObject *callbacks, *test_child; @@ -125,7 +125,7 @@ void screen_update_cell_data(Screen *self, void *address, size_t sz); bool screen_is_cursor_visible(Screen *self); bool screen_selection_range_for_line(Screen *self, index_type y, index_type *start, index_type *end); bool screen_selection_range_for_word(Screen *self, index_type x, index_type y, index_type *start, index_type *end); -void screen_start_selection(Screen *self, index_type x, index_type y); +void screen_start_selection(Screen *self, index_type x, index_type y, bool); void screen_update_selection(Screen *self, index_type x, index_type y, bool ended); bool screen_history_scroll(Screen *self, int amt, bool upwards); Line* screen_visual_line(Screen *self, index_type y);