From 1690e6933f9799a7590cd09a3fe0e4547379e232 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 25 Feb 2020 10:12:30 +0530 Subject: [PATCH] Clean up handling of scrolling in selections --- kitty/screen.c | 224 ++++++++++++++++++------------------------------- kitty/screen.h | 11 ++- 2 files changed, 93 insertions(+), 142 deletions(-) diff --git a/kitty/screen.c b/kitty/screen.c index 91ff947ff..d40dfc76b 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -345,12 +345,8 @@ move_widened_char(Screen *self, CPUCell* cpu_cell, GPUCell *gpu_cell, index_type static inline bool is_selection_empty(Selection *s) { - return s->start.x == s->end.x && s->start.in_left_half_of_cell == s->end.in_left_half_of_cell && s->start.y == s->end.y && s->start_scrolled_by == s->end_scrolled_by; -} - -static inline bool -is_selection_empty_or_out_of_bounds(Screen *self, Selection *s) { - return s->start.x >= self->columns || s->start.y >= self->lines || s->end.x >= self->columns || s->end.y >= self->lines || is_selection_empty(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 @@ -1558,41 +1554,6 @@ screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_dat } -static inline void -selection_coord(const Screen *self, SelectionBoundary *a, unsigned int scrolled_by, SelectionBoundary *ans) { - ans->in_left_half_of_cell = a->in_left_half_of_cell; - if (a->y + self->scrolled_by < scrolled_by) { - ans->x = a->x; ans->y = 0; - } else { - unsigned int y = a->y - scrolled_by + self->scrolled_by; - if (y >= self->lines) { - ans->x = a->x; - if (ans->x > self->columns - 1) { - ans->x = self->columns - 1; - ans->in_left_half_of_cell = false; - } - ans->y = self->lines - 1; - } else { - ans->x = a->x; ans->y = y; - } - } -} - -static inline void -full_selection_coord(Screen *self, SelectionBoundary *a, unsigned int scrolled_by, SelectionBoundary *ans) { - ans->in_left_half_of_cell = a->in_left_half_of_cell; - ans->x = a->x; - if (ans->x >= self->columns) { - ans->x = self->columns - 1; - ans->in_left_half_of_cell = false; - } - ans->y = a->y - scrolled_by; - if (ans->y >= self->lines) { - ans->x = self->columns - 1; ans->y = self->lines - 1; - ans->in_left_half_of_cell = false; - } -} - static inline bool selection_boundary_less_than(SelectionBoundary *a, SelectionBoundary *b) { if (a->y < b->y) return true; @@ -1603,20 +1564,6 @@ selection_boundary_less_than(SelectionBoundary *a, SelectionBoundary *b) { return false; } -#define selection_limits_(which, left, right) { \ - SelectionBoundary a, b; \ - selection_coord(self, &self->which.start, self->which.start_scrolled_by, &a); \ - selection_coord(self, &self->which.end, self->which.end_scrolled_by, &b); \ - *(left) = a; *(right) = b; \ -} - -#define full_selection_limits_(which, left, right) { \ - SelectionBoundary a, b; \ - full_selection_coord(self, &self->which.start, self->which.start_scrolled_by, &a); \ - full_selection_coord(self, &self->which.end, self->which.end_scrolled_by, &b); \ - *(left) = a; *(right) = b; \ -} - typedef Line*(linefunc_t)(Screen*, int); static inline Line* @@ -1643,45 +1590,25 @@ range_line_(Screen *self, int y) { return self->linebuf->line; } -static inline int -clamp_for_range_line(Screen *self, int y) { - if (y < 0) { - unsigned int idx = -(y + 1); - if (idx >= self->historybuf->count) { - y += idx - self->historybuf->count + 1; - } - return y; - } - return MIN((unsigned int)y, self->lines - 1); -} - -typedef struct { - index_type x, x_limit; -} XRange; - -typedef struct { - index_type y, y_limit; - XRange first, body, last; -} IterationData; - static inline bool -selection_is_left_to_right(Selection *self) { +selection_is_left_to_right(const Selection *self) { return self->input_start.x < self->input_current.x || (self->input_start.x == self->input_current.x && self->input_start.in_left_half_of_cell); } -static inline IterationData -iteration_data(const Screen *self, Selection *sel, const bool rectangle) { - IterationData ans = {0}; - SelectionBoundary *start = &sel->start, *end = &sel->end; +static void +iteration_data(const Screen *self, const Selection *sel, IterationData *ans, int min_y) { + memset(ans, 0, sizeof(IterationData)); + const SelectionBoundary *start = &sel->start, *end = &sel->end; + int start_y = (int)start->y - sel->start_scrolled_by, end_y = (int)end->y - sel->end_scrolled_by; // empty selection - if (start->x == end->x && start->y == end->y && start->in_left_half_of_cell == end->in_left_half_of_cell) return ans; + if (start->x == end->x && start_y == end_y && start->in_left_half_of_cell == end->in_left_half_of_cell) return; bool left_to_right = selection_is_left_to_right(sel); - if (rectangle) { + if (sel->rectangle_select) { // empty selection - if (start->x == end->x && (!start->in_left_half_of_cell || end->in_left_half_of_cell)) return ans; + if (start->x == end->x && (!start->in_left_half_of_cell || end->in_left_half_of_cell)) return; - ans.y = MIN(start->y, end->y); ans.y_limit = MAX(start->y, end->y) + 1; + ans->y = MIN(start_y, end_y); ans->y_limit = MAX(start_y, end_y) + 1; index_type x, x_limit; if (start->x == end->x) { @@ -1695,46 +1622,46 @@ iteration_data(const Screen *self, Selection *sel, const bool rectangle) { x_limit = 1 + start->x + (start->in_left_half_of_cell ? -1 : 0); } } - ans.first.x = x; ans.body.x = x; ans.last.x = x; - ans.first.x_limit = x_limit; ans.body.x_limit = x_limit; ans.last.x_limit = x_limit; + ans->first.x = x; ans->body.x = x; ans->last.x = x; + ans->first.x_limit = x_limit; ans->body.x_limit = x_limit; ans->last.x_limit = x_limit; } else { index_type line_limit = self->columns; - if (start->y == end->y) { + if (start_y == end_y) { if (start->x == end->x) { if (start->in_left_half_of_cell && !end->in_left_half_of_cell) { // single cell selection - ans.first.x = start->x; ans.body.x = start->x; ans.last.x = start->x; - ans.first.x_limit = start->x + 1; ans.body.x_limit = start->x + 1; ans.last.x_limit = start->x + 1; - } else return ans; // empty selection + ans->first.x = start->x; ans->body.x = start->x; ans->last.x = start->x; + ans->first.x_limit = start->x + 1; ans->body.x_limit = start->x + 1; ans->last.x_limit = start->x + 1; + } else return; // empty selection } // single line selection if (start->x <= end->x) { - ans.first.x = start->x + (start->in_left_half_of_cell ? 0 : 1); - ans.first.x_limit = 1 + end->x + (end->in_left_half_of_cell ? -1 : 0); + ans->first.x = start->x + (start->in_left_half_of_cell ? 0 : 1); + ans->first.x_limit = 1 + end->x + (end->in_left_half_of_cell ? -1 : 0); } else { - ans.first.x = end->x + (end->in_left_half_of_cell ? 0 : 1); - ans.first.x_limit = 1 + start->x + (start->in_left_half_of_cell ? -1 : 0); + ans->first.x = end->x + (end->in_left_half_of_cell ? 0 : 1); + ans->first.x_limit = 1 + start->x + (start->in_left_half_of_cell ? -1 : 0); } - } else if (start->y < end->y) { - ans.body.x_limit = line_limit; - ans.first.x_limit = line_limit; - ans.first.x = start->x + (start->in_left_half_of_cell ? 0 : 1); - ans.last.x_limit = 1 + end->x + (end->in_left_half_of_cell ? -1 : 0); + } else if (start_y < end_y) { + ans->body.x_limit = line_limit; + ans->first.x_limit = line_limit; + ans->first.x = start->x + (start->in_left_half_of_cell ? 0 : 1); + ans->last.x_limit = 1 + end->x + (end->in_left_half_of_cell ? -1 : 0); } else { - ans.body.x_limit = line_limit; - ans.first.x_limit = line_limit; - ans.first.x = end->x + (end->in_left_half_of_cell ? 0 : 1); - ans.last.x_limit = 1 + start->x + (start->in_left_half_of_cell ? -1 : 0); + ans->body.x_limit = line_limit; + ans->first.x_limit = line_limit; + ans->first.x = end->x + (end->in_left_half_of_cell ? 0 : 1); + ans->last.x_limit = 1 + start->x + (start->in_left_half_of_cell ? -1 : 0); } - ans.y = MIN(start->y, end->y); ans.y_limit = MAX(start->y, end->y) + 1; + ans->y = MIN(start_y, end_y); ans->y_limit = MAX(start_y, end_y) + 1; } - return ans; + ans->y = MAX(ans->y, min_y); } static inline XRange -xrange_for_iteration(const IterationData *idata, const index_type y, const Line *line) { +xrange_for_iteration(const IterationData *idata, const int y, const Line *line) { XRange ans = {.x_limit=xlimit_for_line(line)}; if (y == idata->y) { ans.x_limit = MIN(idata->first.x_limit, ans.x_limit); @@ -1749,24 +1676,37 @@ xrange_for_iteration(const IterationData *idata, const index_type y, const Line return ans; } +static inline bool +iteration_data_is_empty(const Screen *self, const IterationData *idata) { + if (idata->y >= idata->y_limit) return true; + index_type xl = MIN(idata->first.x_limit, self->columns); + if (idata->first.x < xl) return false; + xl = MIN(idata->body.x_limit, self->columns); + if (idata->body.x < xl) return false; + xl = MIN(idata->last.x_limit, self->columns); + if (idata->last.x < xl) return false; + return true; +} static inline void -apply_selection(Screen *self, uint8_t *data, Selection *s, uint8_t set_mask, bool rectangle_select) { - if (is_selection_empty_or_out_of_bounds(self, s)) return; - IterationData idata = iteration_data(self, s, rectangle_select); - for (index_type y = idata.y; y < idata.y_limit; y++) { +apply_selection(Screen *self, uint8_t *data, const Selection *s, IterationData *last_rendered, uint8_t set_mask) { + iteration_data(self, s, last_rendered, 0); + + for (int y = last_rendered->y; y < last_rendered->y_limit; y++) { Line *line = visual_line_(self, y); uint8_t *line_start = data + self->columns * y; - XRange xr = xrange_for_iteration(&idata, y, line); + XRange xr = xrange_for_iteration(last_rendered, y, line); for (index_type x = xr.x; x < xr.x_limit; x++) line_start[x] |= set_mask; } } bool screen_has_selection(Screen *self) { - Selection s = {0}; - selection_limits_(selection, &s.start, &s.end); - return !is_selection_empty_or_out_of_bounds(self, &s); + if (is_selection_empty(&self->selection)) return false; + IterationData idata; + iteration_data(self, &self->selection, &idata, 0); + if (iteration_data_is_empty(self, &idata)) return false; + return true; } void @@ -1774,19 +1714,18 @@ screen_apply_selection(Screen *self, void *address, size_t size) { memset(address, 0, 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, 1, self->selection.rectangle_select); - selection_limits_(url_range, &self->last_rendered.url.start, &self->last_rendered.url.end); - apply_selection(self, address, &self->last_rendered.url, 2, false); + apply_selection(self, address, &self->selection, &self->last_rendered.selection, 1); + apply_selection(self, address, &self->url_range, &self->last_rendered.url, 2); } static inline PyObject* -text_for_range(Screen *self, Selection *sel, bool rectangle_select, bool insert_newlines, linefunc_t line_func) { - IterationData idata = iteration_data(self, sel, rectangle_select); +text_for_range(Screen *self, const Selection *sel, bool insert_newlines) { + IterationData idata; + iteration_data(self, sel, &idata, -self->historybuf->count); PyObject *ans = PyTuple_New(idata.y_limit - idata.y); if (!ans) return NULL; - for (index_type y = idata.y, i = 0; y < idata.y_limit; y++, i++) { - Line *line = line_func(self, y); + for (int i = 0, y = idata.y; y < idata.y_limit; y++, i++) { + Line *line = range_line_(self, y); XRange xr = xrange_for_iteration(&idata, y, line); char leading_char = (i > 0 && insert_newlines && !line->continued) ? '\n' : 0; PyObject *text = unicode_in_range(line, xr.x, xr.x_limit, true, leading_char); @@ -1798,14 +1737,20 @@ text_for_range(Screen *self, Selection *sel, bool rectangle_select, bool insert_ bool screen_open_url(Screen *self) { - Selection s = {0}; - selection_limits_(url_range, &s.start, &s.end); - if (is_selection_empty_or_out_of_bounds(self, &s)) return false; - s.input_start = s.start; s.input_current = s.end; - PyObject *url = text_for_range(self, &s, false, false, visual_line_); - if (url) { call_boss(open_url_lines, "(O)", url); Py_CLEAR(url); } - else PyErr_Print(); - return true; + if (is_selection_empty(&self->url_range)) return false; + PyObject *lines = text_for_range(self, &self->url_range, 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); + } + Py_CLEAR(lines); + } + if (PyErr_Occurred()) PyErr_Print(); + return ret; } static void @@ -2139,11 +2084,7 @@ copy_colors_from(Screen *self, Screen *other) { static PyObject* text_for_selection(Screen *self, PyObject *a UNUSED) { - Selection s = self->selection; - full_selection_limits_(selection, &s.start, &s.end); - s.start.y = clamp_for_range_line(self, s.start.y); - s.end.y = clamp_for_range_line(self, s.end.y); - return text_for_range(self, &s, self->selection.rectangle_select, true, range_line_); + return text_for_range(self, &self->selection, true); } bool @@ -2238,11 +2179,11 @@ scroll(Screen *self, PyObject *args) { bool screen_is_selection_dirty(Screen *self) { - SelectionBoundary start, end; - selection_limits_(selection, &start, &end); - if (self->last_selection_scrolled_by != self->scrolled_by || start.x != self->last_rendered.selection.start.x || start.y != self->last_rendered.selection.start.y || end.x != self->last_rendered.selection.end.x || end.y != self->last_rendered.selection.end.y || !self->selection_updated_once) return true; - selection_limits_(url_range, &start, &end); - if (start.x != self->last_rendered.url.start.x || start.y != self->last_rendered.url.start.y || end.x != self->last_rendered.url.end.x || end.y != self->last_rendered.url.end.y) return true; + IterationData q; + iteration_data(self, &self->selection, &q, 0); + if (memcmp(&q, &self->last_rendered.selection, sizeof(IterationData)) != 0) return true; + iteration_data(self, &self->url_range, &q, 0); + if (memcmp(&q, &self->last_rendered.url, sizeof(IterationData)) != 0) return true; return false; } @@ -2269,6 +2210,7 @@ screen_update_selection(Screen *self, index_type x, index_type y, bool in_left_h if (ended) self->selection.in_progress = false; 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; SelectionBoundary start, end, *a, *b; a = &self->selection.start, b = &self->selection.end; bool left_to_right = selection_is_left_to_right(&self->selection); diff --git a/kitty/screen.h b/kitty/screen.h index e28408cb3..a607eddc6 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -63,6 +63,15 @@ 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 @@ -76,7 +85,7 @@ typedef struct { unsigned int current_charset; Selection selection, url_range; struct { - Selection selection, url; + IterationData selection, url; } last_rendered; bool use_latin1, selection_updated_once, is_dirty, scroll_changed, reload_all_gpu_data; Cursor *cursor;