From 00ea189074c1ba46949d1a1087ea2939f6cfc216 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 27 Aug 2020 21:20:53 +0530 Subject: [PATCH] Infrastructure to support multiple selection ranges Needed for hyperlinks, that can be discontinuous --- kitty/mouse.c | 7 +- kitty/screen.c | 261 ++++++++++++++++++++++++++++++++----------------- kitty/screen.h | 32 +++--- 3 files changed, 194 insertions(+), 106 deletions(-) diff --git a/kitty/mouse.c b/kitty/mouse.c index 3ed5ff3cf..896cccdef 100644 --- a/kitty/mouse.c +++ b/kitty/mouse.c @@ -202,14 +202,15 @@ update_drag(bool from_button, Window *w, bool is_release, int modifiers) { if (is_release) { global_state.active_drag_in_window = 0; w->last_drag_scroll_at = 0; - if (screen->selection.in_progress) + if (screen->selections.in_progress) { screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, true, false); + } } else { global_state.active_drag_in_window = w->id; screen_start_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, modifiers == (int)OPT(rectangle_select_modifiers) || modifiers == ((int)OPT(rectangle_select_modifiers) | (int)OPT(terminal_select_modifiers)), EXTEND_CELL); } - } else if (screen->selection.in_progress) { + } else if (screen->selections.in_progress) { screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, false); } } @@ -331,7 +332,7 @@ detect_url(Screen *screen, unsigned int x, unsigned int y) { static inline void handle_mouse_movement_in_kitty(Window *w, int button, bool mouse_cell_changed) { Screen *screen = w->render_data.screen; - if (screen->selection.in_progress && (button == GLFW_MOUSE_BUTTON_LEFT || button == GLFW_MOUSE_BUTTON_RIGHT)) { + if (screen->selections.in_progress && (button == GLFW_MOUSE_BUTTON_LEFT || button == GLFW_MOUSE_BUTTON_RIGHT)) { monotonic_t now = monotonic(); if ((now - w->last_drag_scroll_at) >= ms_to_monotonic_t(20ll) || mouse_cell_changed) { update_drag(false, w, false, 0); diff --git a/kitty/screen.c b/kitty/screen.c index fa1711a20..3327149f4 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -26,12 +26,18 @@ #include "charsets.h" static const ScreenModes empty_modes = {0, .mDECAWM=true, .mDECTCEM=true, .mDECARM=true}; -static Selection EMPTY_SELECTION = {{0}}; #define CSI_REP_MAX_REPETITIONS 65535u // Constructor/destructor {{{ +static inline void +clear_selection(Selections *selections) { + selections->in_progress = false; + selections->extend_mode = EXTEND_CELL; + selections->count = 0; +} + static inline void init_tabstops(bool *tabstops, index_type count) { // In terminfo we specify the number of initial tabstops (it) as 8 @@ -158,7 +164,8 @@ screen_reset(Screen *self) { init_tabstops(self->alt_tabstops, self->columns); cursor_reset(self->cursor); self->is_dirty = true; - self->selection = EMPTY_SELECTION; + clear_selection(&self->selections); + clear_selection(&self->url_ranges); screen_cursor_position(self, 1, 1); set_dynamic_color(self, 110, NULL); set_dynamic_color(self, 111, NULL); @@ -246,8 +253,8 @@ screen_resize(Screen *self, unsigned int lines, unsigned int columns) { init_tabstops(self->main_tabstops, self->columns); init_tabstops(self->alt_tabstops, self->columns); self->is_dirty = true; - self->selection = EMPTY_SELECTION; - self->url_range = EMPTY_SELECTION; + clear_selection(&self->selections); + clear_selection(&self->url_ranges); /* printf("old_cursor: (%u, %u) new_cursor: (%u, %u) beyond_content: %d\n", self->cursor->x, self->cursor->y, cursor_x, cursor_y, cursor_is_beyond_content); */ self->cursor->x = MIN(cursor_x, self->columns - 1); self->cursor->y = MIN(cursor_y, self->lines - 1); @@ -292,6 +299,8 @@ dealloc(Screen* self) { PyMem_Free(self->overlay_line.gpu_cells); PyMem_Free(self->main_tabstops); free(self->pending_mode.buf); + free(self->selections.items); + free(self->url_ranges.items); free_hyperlink_pool(self->hyperlink_pool); Py_TYPE(self)->tp_free((PyObject*)self); } // }}} @@ -352,17 +361,22 @@ move_widened_char(Screen *self, CPUCell* cpu_cell, GPUCell *gpu_cell, index_type } static inline bool -is_selection_empty(Selection *s) { +is_selection_empty(const Selection *s) { int start_y = (int)s->start.y - (int)s->start_scrolled_by, end_y = (int)s->end.y - (int)s->end_scrolled_by; return s->start.x == s->end.x && s->start.in_left_half_of_cell == s->end.in_left_half_of_cell && start_y == end_y; } static inline bool -selection_has_screen_line(Selection *s, int y) { - if (is_selection_empty(s)) return false; - int top = (int)s->start.y - s->start_scrolled_by; - int bottom = (int)s->end.y - s->end_scrolled_by; - return top <= y && y <= bottom; +selection_has_screen_line(const Selections *selections, const int y) { + for (size_t i = 0; i < selections->count; i++) { + const Selection *s = selections->items + i; + if (!is_selection_empty(s)) { + int top = (int)s->start.y - s->start_scrolled_by; + int bottom = (int)s->end.y - s->end_scrolled_by; + if (top <= y && y <= bottom) return true; + } + } + return false; } void @@ -419,7 +433,7 @@ draw_second_flag_codepoint(Screen *self, char_type ch) { if (!is_flag_pair(cell->ch, ch) || cell->cc_idx[0]) return false; line_add_combining_char(self->linebuf->line, ch, xpos); self->is_dirty = true; - if (selection_has_screen_line(&self->selection, ypos)) self->selection = EMPTY_SELECTION; + if (selection_has_screen_line(&self->selections, ypos)) clear_selection(&self->selections); linebuf_mark_line_dirty(self->linebuf, ypos); return true; } @@ -453,7 +467,7 @@ draw_combining_char(Screen *self, char_type ch) { if (has_prev_char) { line_add_combining_char(self->linebuf->line, ch, xpos); self->is_dirty = true; - if (selection_has_screen_line(&self->selection, ypos)) self->selection = EMPTY_SELECTION; + if (selection_has_screen_line(&self->selections, ypos)) clear_selection(&self->selections); linebuf_mark_line_dirty(self->linebuf, ypos); if (ch == 0xfe0f) { // emoji presentation variation marker makes default text presentation emoji (narrow emoji) into wide emoji CPUCell *cpu_cell = self->linebuf->line->cpu_cells + xpos; @@ -523,7 +537,7 @@ screen_draw(Screen *self, uint32_t och) { self->cursor->x++; } self->is_dirty = true; - if (selection_has_screen_line(&self->selection, self->cursor->y)) self->selection = EMPTY_SELECTION; + if (selection_has_screen_line(&self->selections, self->cursor->y)) clear_selection(&self->selections); linebuf_mark_line_dirty(self->linebuf, self->cursor->y); } @@ -704,7 +718,7 @@ screen_toggle_screen_buffer(Screen *self, bool save_cursor, bool clear_alt_scree } screen_history_scroll(self, SCROLL_FULL, false); self->is_dirty = true; - self->selection = EMPTY_SELECTION; + clear_selection(&self->selections); } void screen_normal_keypad_mode(Screen UNUSED *self) {} // Not implemented as this is handled by the GUI @@ -948,18 +962,22 @@ screen_cursor_to_column(Screen *self, unsigned int column) { } static inline void -index_selection(Screen *self, Selection *s, bool up) { - if (is_selection_empty(s)) return; - if (up) { - if (s->start.y == 0) s->start_scrolled_by += 1; - else s->start.y--; - if (s->end.y == 0) s->end_scrolled_by += 1; - else s->end.y--; - } else { - if (s->start.y >= self->lines - 1) s->start_scrolled_by -= 1; - else s->start.y++; - if (s->end.y >= self->lines - 1) s->end_scrolled_by -= 1; - else s->end.y++; +index_selection(const Screen *self, Selections *selections, bool up) { + for (size_t i = 0; i < selections->count; i++) { + Selection *s = selections->items + i; + if (!is_selection_empty(s)) { + if (up) { + if (s->start.y == 0) s->start_scrolled_by += 1; + else s->start.y--; + if (s->end.y == 0) s->end_scrolled_by += 1; + else s->end.y--; + } else { + if (s->start.y >= self->lines - 1) s->start_scrolled_by -= 1; + else s->start.y++; + if (s->end.y >= self->lines - 1) s->end_scrolled_by -= 1; + else s->end.y++; + } + } } } @@ -984,7 +1002,7 @@ index_selection(Screen *self, Selection *s, bool up) { } \ linebuf_clear_line(self->linebuf, bottom); \ self->is_dirty = true; \ - index_selection(self, &self->selection, true); + index_selection(self, &self->selections, true); void screen_index(Screen *self) { @@ -1011,7 +1029,7 @@ screen_scroll(Screen *self, unsigned int count) { linebuf_clear_line(self->linebuf, top); \ INDEX_GRAPHICS(1) \ self->is_dirty = true; \ - index_selection(self, &self->selection, false); + index_selection(self, &self->selections, false); void screen_reverse_index(Screen *self) { @@ -1195,7 +1213,7 @@ screen_erase_in_line(Screen *self, unsigned int how, bool private) { line_apply_cursor(self->linebuf->line, self->cursor, s, n, true); } self->is_dirty = true; - if (selection_has_screen_line(&self->selection, self->cursor->y)) self->selection = EMPTY_SELECTION; + if (selection_has_screen_line(&self->selections, self->cursor->y)) clear_selection(&self->selections); linebuf_mark_line_dirty(self->linebuf, self->cursor->y); } } @@ -1240,7 +1258,7 @@ screen_erase_in_display(Screen *self, unsigned int how, bool private) { linebuf_mark_line_as_not_continued(self->linebuf, i); } self->is_dirty = true; - self->selection = EMPTY_SELECTION; + clear_selection(&self->selections); } if (how != 2) { screen_erase_in_line(self, how, private); @@ -1262,7 +1280,7 @@ screen_insert_lines(Screen *self, unsigned int count) { if (top <= self->cursor->y && self->cursor->y <= bottom) { linebuf_insert_lines(self->linebuf, count, self->cursor->y, bottom); self->is_dirty = true; - self->selection = EMPTY_SELECTION; + clear_selection(&self->selections); screen_carriage_return(self); } } @@ -1283,7 +1301,7 @@ screen_delete_lines(Screen *self, unsigned int count) { if (top <= self->cursor->y && self->cursor->y <= bottom) { linebuf_delete_lines(self->linebuf, count, self->cursor->y, bottom); self->is_dirty = true; - self->selection = EMPTY_SELECTION; + clear_selection(&self->selections); screen_carriage_return(self); } } @@ -1300,7 +1318,7 @@ screen_insert_characters(Screen *self, unsigned int count) { line_apply_cursor(self->linebuf->line, self->cursor, x, num, true); linebuf_mark_line_dirty(self->linebuf, self->cursor->y); self->is_dirty = true; - if (selection_has_screen_line(&self->selection, self->cursor->y)) self->selection = EMPTY_SELECTION; + if (selection_has_screen_line(&self->selections, self->cursor->y)) clear_selection(&self->selections); } } @@ -1338,7 +1356,7 @@ screen_delete_characters(Screen *self, unsigned int count) { line_apply_cursor(self->linebuf->line, self->cursor, self->columns - num, num, true); linebuf_mark_line_dirty(self->linebuf, self->cursor->y); self->is_dirty = true; - if (selection_has_screen_line(&self->selection, self->cursor->y)) self->selection = EMPTY_SELECTION; + if (selection_has_screen_line(&self->selections, self->cursor->y)) clear_selection(&self->selections); } } @@ -1352,7 +1370,7 @@ screen_erase_characters(Screen *self, unsigned int count) { line_apply_cursor(self->linebuf->line, self->cursor, x, num, true); linebuf_mark_line_dirty(self->linebuf, self->cursor->y); self->is_dirty = true; - if (selection_has_screen_line(&self->selection, self->cursor->y)) self->selection = EMPTY_SELECTION; + if (selection_has_screen_line(&self->selections, self->cursor->y)) clear_selection(&self->selections); } // }}} @@ -1677,9 +1695,7 @@ screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_dat } update_line_data(self->linebuf->line, y, address); } - if (was_dirty) { - self->url_range = EMPTY_SELECTION; - } + if (was_dirty) clear_selection(&self->url_ranges); } @@ -1821,32 +1837,42 @@ iteration_data_is_empty(const Screen *self, const IterationData *idata) { } static inline void -apply_selection(Screen *self, uint8_t *data, const Selection *s, IterationData *last_rendered, uint8_t set_mask) { - iteration_data(self, s, last_rendered, -self->historybuf->count, true); +apply_selection(Screen *self, uint8_t *data, Selection *s, uint8_t set_mask) { + iteration_data(self, s, &s->last_rendered, -self->historybuf->count, true); - for (int y = MAX(0, last_rendered->y); y < last_rendered->y_limit && y < (int)self->lines; y++) { + for (int y = MAX(0, s->last_rendered.y); y < s->last_rendered.y_limit && y < (int)self->lines; y++) { Line *line = visual_line_(self, y); uint8_t *line_start = data + self->columns * y; - XRange xr = xrange_for_iteration(last_rendered, y, line); + XRange xr = xrange_for_iteration(&s->last_rendered, y, line); for (index_type x = xr.x; x < xr.x_limit; x++) line_start[x] |= set_mask; } - last_rendered->y = MAX(0, last_rendered->y); + s->last_rendered.y = MAX(0, s->last_rendered.y); } bool screen_has_selection(Screen *self) { - if (is_selection_empty(&self->selection)) return false; IterationData idata; - iteration_data(self, &self->selection, &idata, -self->historybuf->count, true); - if (iteration_data_is_empty(self, &idata)) return false; - return true; + for (size_t i = 0; i < self->selections.count; i++) { + Selection *s = self->selections.items + i; + if (!is_selection_empty(s)) { + iteration_data(self, s, &idata, -self->historybuf->count, true); + if (!iteration_data_is_empty(self, &idata)) return true; + } + } + return false; } void screen_apply_selection(Screen *self, void *address, size_t size) { memset(address, 0, size); - apply_selection(self, address, &self->selection, &self->last_rendered.selection, 1); - apply_selection(self, address, &self->url_range, &self->last_rendered.url, 2); + for (size_t i = 0; i < self->selections.count; i++) { + apply_selection(self, address, self->selections.items + i, 1); + } + self->selections.last_rendered_count = self->selections.count; + for (size_t i = 0; i < self->url_ranges.count; i++) { + apply_selection(self, address, self->url_ranges.items + i, 2); + } + self->url_ranges.last_rendered_count = self->url_ranges.count; } static inline PyObject* @@ -1866,20 +1892,46 @@ text_for_range(Screen *self, const Selection *sel, bool insert_newlines) { return ans; } + +static inline PyObject* +extend_tuple(PyObject *a, PyObject *b) { + Py_ssize_t bs = PyBytes_GET_SIZE(b); + if (bs < 1) return a; + Py_ssize_t off = PyTuple_GET_SIZE(a); + if (_PyTuple_Resize(&a, off + bs) != 0) return NULL; + for (Py_ssize_t y = 0; y < bs; y++) { + PyObject *t = PyTuple_GET_ITEM(b, y); + Py_INCREF(t); + PyTuple_SET_ITEM(a, off + y, t); + } + return a; +} + + bool screen_open_url(Screen *self) { - if (is_selection_empty(&self->url_range)) return false; - PyObject *lines = text_for_range(self, &self->url_range, false); + if (!self->url_ranges.count) return false; bool ret = false; - if (lines) { - PyObject *joiner = PyUnicode_FromString(""); - if (joiner) { - PyObject *url = PyUnicode_Join(joiner, lines); - if (url) { call_boss(open_url_lines, "(O)", url); Py_CLEAR(url); ret = true; } - Py_CLEAR(joiner); + PyObject *lines = NULL; + for (size_t i = 0; i < self->url_ranges.count; i++) { + // we dont care about the order of the ranges because the text only matters + // for text based urls not hyperlinks and for the former there is only ever a single range + Selection *s = self->url_ranges.items + i; + if (!is_selection_empty(s)) { + PyObject *temp = text_for_range(self, s, false); + if (temp) { + if (lines) { + lines = extend_tuple(lines, temp); + Py_DECREF(temp); + } else lines = temp; + } else break; } - Py_CLEAR(lines); } + if (lines && PyTuple_GET_SIZE(lines) > 0) { + call_boss(open_url_lines, "(O)", lines); + ret = true; + } + Py_CLEAR(lines); if (PyErr_Occurred()) PyErr_Print(); return ret; } @@ -2210,8 +2262,8 @@ update_selection(Screen *self, PyObject *args) { } static PyObject* -clear_selection(Screen *self, PyObject *args UNUSED) { - self->selection = EMPTY_SELECTION; +clear_selection_(Screen *s, PyObject *args UNUSED) { + clear_selection(&s->selections); Py_RETURN_NONE; } @@ -2239,9 +2291,8 @@ start_selection(Screen *self, PyObject *args) { static PyObject* is_rectangle_select(Screen *self, PyObject *a UNUSED) { - PyObject *ans = self->selection.rectangle_select ? Py_True : Py_False; - Py_INCREF(ans); - return ans; + if (self->selections.count && self->selections.items[0].rectangle_select) Py_RETURN_TRUE; + Py_RETURN_FALSE; } static PyObject* @@ -2252,7 +2303,19 @@ copy_colors_from(Screen *self, Screen *other) { static PyObject* text_for_selection(Screen *self, PyObject *a UNUSED) { - return text_for_range(self, &self->selection, true); + PyObject *lines = NULL; + for (size_t i = 0; i < self->selections.count; i++) { + PyObject *temp = text_for_range(self, self->selections.items + i, true); + if (temp) { + if (lines) { + lines = extend_tuple(lines, temp); + Py_DECREF(temp); + } else lines = temp; + } else break; + } + if (PyErr_Occurred()) { Py_CLEAR(lines); return NULL; } + if (!lines) lines = PyTuple_New(0); + return lines; } bool @@ -2359,18 +2422,28 @@ bool screen_is_selection_dirty(Screen *self) { IterationData q; if (self->scrolled_by != self->last_rendered.scrolled_by) return true; - iteration_data(self, &self->selection, &q, 0, true); - if (memcmp(&q, &self->last_rendered.selection, sizeof(IterationData)) != 0) return true; - iteration_data(self, &self->url_range, &q, 0, true); - if (memcmp(&q, &self->last_rendered.url, sizeof(IterationData)) != 0) return true; + if (self->selections.last_rendered_count != self->selections.count || self->url_ranges.last_rendered_count != self->url_ranges.count) return true; + for (size_t i = 0; i < self->selections.count; i++) { + iteration_data(self, self->selections.items + i, &q, 0, true); + if (memcmp(&q, &self->selections.items[i].last_rendered, sizeof(IterationData)) != 0) return true; + } + for (size_t i = 0; i < self->url_ranges.count; i++) { + iteration_data(self, self->url_ranges.items + i, &q, 0, true); + if (memcmp(&q, &self->url_ranges.items[i].last_rendered, sizeof(IterationData)) != 0) return true; + } return false; } void screen_start_selection(Screen *self, index_type x, index_type y, bool in_left_half_of_cell, bool rectangle_select, SelectionExtendMode extend_mode) { -#define A(attr, val) self->selection.attr = val; +#define A(attr, val) self->selections.items->attr = val; + ensure_space_for(&self->selections, items, Selection, 1, capacity, 1, false); + memset(self->selections.items, 0, sizeof(Selection)); + self->selections.count = 1; + self->selections.in_progress = true; + self->selections.extend_mode = extend_mode; 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); A(rectangle_select, rectangle_select); A(extend_mode, extend_mode); A(start.in_left_half_of_cell, in_left_half_of_cell); A(end.in_left_half_of_cell, in_left_half_of_cell); + A(rectangle_select, rectangle_select); A(start.in_left_half_of_cell, in_left_half_of_cell); A(end.in_left_half_of_cell, in_left_half_of_cell); A(input_start.x, x); A(input_start.y, y); A(input_start.in_left_half_of_cell, in_left_half_of_cell); A(input_current.x, x); A(input_current.y, y); A(input_current.in_left_half_of_cell, in_left_half_of_cell); #undef A @@ -2378,25 +2451,33 @@ screen_start_selection(Screen *self, index_type x, index_type y, bool in_left_ha void screen_mark_url(Screen *self, index_type start_x, index_type start_y, index_type end_x, index_type end_y) { -#define A(attr, val) self->url_range.attr = val; - A(start.x, start_x); A(end.x, end_x); A(start.y, start_y); A(end.y, end_y); A(start_scrolled_by, self->scrolled_by); A(end_scrolled_by, self->scrolled_by); - A(start.in_left_half_of_cell, true); A(end.in_left_half_of_cell, start_x == end_x && start_y == end_y); +#define A(attr, val) self->url_ranges.items->attr = val; + ensure_space_for(&self->url_ranges, items, Selection, 1, capacity, 8, false); + memset(self->url_ranges.items, 0, sizeof(Selection)); + if (!start_x && !start_y && !end_x && !end_y) self->url_ranges.count = 0; + else { + self->url_ranges.count = 1; + A(start.x, start_x); A(end.x, end_x); A(start.y, start_y); A(end.y, end_y); A(start_scrolled_by, self->scrolled_by); A(end_scrolled_by, self->scrolled_by); + A(start.in_left_half_of_cell, true); A(end.in_left_half_of_cell, start_x == end_x && start_y == end_y); + } #undef A } void screen_update_selection(Screen *self, index_type x, index_type y, bool in_left_half_of_cell, bool ended, bool start_extended_selection) { - self->selection.in_progress = !ended; - self->selection.input_current.x = x; self->selection.input_current.y = y; - self->selection.input_current.in_left_half_of_cell = in_left_half_of_cell; - self->selection.end_scrolled_by = self->scrolled_by; + if (!self->selections.count) return; + self->selections.in_progress = !ended; + Selection *s = self->selections.items; + s->input_current.x = x; s->input_current.y = y; + s->input_current.in_left_half_of_cell = in_left_half_of_cell; + s->end_scrolled_by = self->scrolled_by; SelectionBoundary start, end, *a, *b; - a = &self->selection.start, b = &self->selection.end; + a = &s->start, b = &s->end; - switch(self->selection.extend_mode) { + switch(self->selections.extend_mode) { case EXTEND_WORD: { - SelectionBoundary *before = &self->selection.input_start, *after = &self->selection.input_current; - if (selection_boundary_less_than(after, before)) { before = after; after = &self->selection.input_start; } + SelectionBoundary *before = &s->input_start, *after = &s->input_current; + if (selection_boundary_less_than(after, before)) { before = after; after = &s->input_start; } bool found_at_start = screen_selection_range_for_word(self, before->x, before->y, &start.y, &end.y, &start.x, &end.x, true); if (found_at_start) { a->x = start.x; a->y = start.y; a->in_left_half_of_cell = true; @@ -2411,13 +2492,13 @@ screen_update_selection(Screen *self, index_type x, index_type y, bool in_left_h } case EXTEND_LINE: { index_type top_line, bottom_line; - if (start_extended_selection || y == self->selection.start.y) { + if (start_extended_selection || y == s->start.y) { top_line = y; bottom_line = y; - } else if (y < self->selection.start.y) { - top_line = y; bottom_line = self->selection.start.y; - a = &self->selection.end; b = &self->selection.start; - } else if (y > self->selection.start.y) { - bottom_line = y; top_line = self->selection.start.y; + } else if (y < s->start.y) { + top_line = y; bottom_line = s->start.y; + a = &s->end; b = &s->start; + } else if (y > s->start.y) { + bottom_line = y; top_line = s->start.y; } else break; while (top_line > 0 && visual_line_(self, top_line)->continued) { if (!screen_selection_range_for_line(self, top_line - 1, &start.x, &end.x)) break; @@ -2435,10 +2516,10 @@ screen_update_selection(Screen *self, index_type x, index_type y, bool in_left_h break; } case EXTEND_CELL: - self->selection.end.x = x; self->selection.end.y = y; self->selection.end.in_left_half_of_cell = in_left_half_of_cell; + s->end.x = x; s->end.y = y; s->end.in_left_half_of_cell = in_left_half_of_cell; break; } - if (!self->selection.in_progress) call_boss(set_primary_selection, NULL); + if (!self->selections.in_progress) call_boss(set_primary_selection, NULL); } static PyObject* @@ -2679,7 +2760,7 @@ static PyMethodDef methods[] = { MND(clear_tab_stop, METH_VARARGS) MND(start_selection, METH_VARARGS) MND(update_selection, METH_VARARGS) - MND(clear_selection, METH_NOARGS) + {"clear_selection", (PyCFunction)clear_selection_, METH_NOARGS, ""}, MND(reverse_index, METH_NOARGS) MND(mark_as_dirty, METH_NOARGS) MND(resize, METH_VARARGS) diff --git a/kitty/screen.h b/kitty/screen.h index faa8d06cd..602dd0048 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -26,13 +26,29 @@ typedef struct { typedef enum SelectionExtendModes { EXTEND_CELL, EXTEND_WORD, EXTEND_LINE } SelectionExtendMode; +typedef struct { + index_type x, x_limit; +} XRange; + +typedef struct { + int y, y_limit; + XRange first, body, last; +} IterationData; + typedef struct { SelectionBoundary start, end, input_start, input_current; unsigned int start_scrolled_by, end_scrolled_by; - bool in_progress, rectangle_select; - SelectionExtendMode extend_mode; + bool rectangle_select; + IterationData last_rendered; } Selection; +typedef struct { + Selection *items; + size_t count, capacity, last_rendered_count; + bool in_progress; + SelectionExtendMode extend_mode; +} Selections; + #define SAVEPOINTS_SZ 256 typedef struct { @@ -63,15 +79,6 @@ typedef struct { index_type xstart, ynum, xnum; } OverlayLine; -typedef struct { - index_type x, x_limit; -} XRange; - -typedef struct { - int y, y_limit; - XRange first, body, last; -} IterationData; - typedef struct { PyObject_HEAD @@ -82,9 +89,8 @@ typedef struct { id_type window_id; uint32_t utf8_state, utf8_codepoint, *g0_charset, *g1_charset, *g_charset; unsigned int current_charset; - Selection selection, url_range; + Selections selections, url_ranges; struct { - IterationData selection, url; unsigned int cursor_x, cursor_y, scrolled_by; index_type lines, columns; } last_rendered;