From 408c719c5f8b752a61d02e427824fb23e84cf64e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 10 Sep 2017 13:56:22 +0530 Subject: [PATCH] Move selection tracking into the Screen class --- kitty/char_grid.py | 76 +++++++------------------------ kitty/data-types.h | 6 +++ kitty/screen.c | 111 ++++++++++++++++++++++++++++++++++++++------- kitty/window.py | 2 +- 4 files changed, 117 insertions(+), 78 deletions(-) diff --git a/kitty/char_grid.py b/kitty/char_grid.py index f214bea84..6e5154b82 100644 --- a/kitty/char_grid.py +++ b/kitty/char_grid.py @@ -63,35 +63,6 @@ def load_shader_programs(): # }}} -class Selection: # {{{ - - __slots__ = tuple('in_progress start_x start_y start_scrolled_by end_x end_y end_scrolled_by'.split()) - - def __init__(self): - self.clear() - - def clear(self): - self.in_progress = False - self.start_x = self.start_y = self.end_x = self.end_y = 0 - self.start_scrolled_by = self.end_scrolled_by = 0 - - def limits(self, scrolled_by, lines, columns): - - def coord(x, y, ydelta): - y = y - ydelta + scrolled_by - if y < 0: - x, y = 0, 0 - elif y >= lines: - x, y = columns - 1, lines - 1 - return x, y - - a = coord(self.start_x, self.start_y, self.start_scrolled_by) - b = coord(self.end_x, self.end_y, self.end_scrolled_by) - return (a, b) if a[1] < b[1] or (a[1] == b[1] and a[0] <= b[0]) else (b, a) - -# }}} - - def calculate_gl_geometry(window_geometry, viewport_width, viewport_height, cell_width, cell_height): dx, dy = 2 * cell_width / viewport_width, 2 * cell_height / viewport_height xmargin = window_geometry.left / viewport_width @@ -124,7 +95,6 @@ class CharGrid: def __init__(self, screen, opts): self.vao_id = None - self.current_selection = Selection() self.screen_reversed = False self.last_rendered_selection = None self.render_data = None @@ -152,7 +122,7 @@ class CharGrid: self.update_position(window_geometry) self.data_buffer_size = self.screen_geometry.ynum * self.screen_geometry.xnum * CELL['size'] self.selection_buffer_size = self.screen_geometry.ynum * self.screen_geometry.xnum * sizeof(GLfloat) - self.current_selection.clear() + self.screen.clear_selection() def change_colors(self, changes): dirtied = False @@ -175,13 +145,9 @@ class CharGrid: def update_cell_data(self, cell_program): if self.data_buffer_size is None: return - clear_selection = self.screen.is_dirty with cell_program.mapped_vertex_data(self.vao_id, self.data_buffer_size) as address: cursor_changed, self.screen_reversed = self.screen.update_cell_data( address, False) - - if clear_selection: - self.current_selection.clear() self.render_data = self.screen_geometry if cursor_changed: c = self.screen.cursor @@ -200,16 +166,11 @@ class CharGrid: y = 0 if my <= cell_size.height else self.screen.lines - 1 ps = None if is_press: - self.current_selection.start_x = self.current_selection.end_x = x - self.current_selection.start_y = self.current_selection.end_y = y - self.current_selection.start_scrolled_by = self.current_selection.end_scrolled_by = self.screen.scrolled_by - self.current_selection.in_progress = True - elif self.current_selection.in_progress: - self.current_selection.end_x = x - self.current_selection.end_y = y - self.current_selection.end_scrolled_by = self.screen.scrolled_by - if is_press is False: - self.current_selection.in_progress = False + self.screen.start_selection(x, y) + elif self.screen.is_selection_in_progress(): + ended = is_press is False + self.screen.update_selection(x, y, ended) + if ended: ps = self.text_for_selection() if ps and ps.strip(): set_primary_selection(ps) @@ -251,16 +212,14 @@ class CharGrid: if x is not None: line = self.screen.visual_line(y) if line is not None and count in (2, 3): - s = self.current_selection - s.start_scrolled_by = s.end_scrolled_by = self.scrolled_by - s.start_y = s.end_y = y - s.in_progress = False if count == 2: - s.start_x, xlimit = self.screen.selection_range_for_word(x, y, self.opts.select_by_word_characters) - s.end_x = max(s.start_x, xlimit - 1) + start_x, xlimit = self.screen.selection_range_for_word(x, y, self.opts.select_by_word_characters) + end_x = max(start_x, xlimit - 1) elif count == 3: - s.start_x, xlimit = self.screen.selection_range_for_line(y) - s.end_x = max(s.start_x, xlimit - 1) + start_x, xlimit = self.screen.selection_range_for_line(y) + end_x = max(start_x, xlimit - 1) + self.screen.start_selection(start_x, y) + self.screen.update_selection(end_x, y, True) ps = self.text_for_selection() if ps: set_primary_selection(ps) @@ -271,10 +230,8 @@ class CharGrid: self.screen.linebuf.as_ansi(ans.append) return ''.join(ans).encode('utf-8') - def text_for_selection(self, sel=None): - s = sel or self.current_selection - start, end = s.limits(self.screen.scrolled_by, self.screen.lines, self.screen.columns) - return ''.join(self.screen.text_for_selection(*start, *end)) + def text_for_selection(self): + return ''.join(self.screen.text_for_selection()) def prepare_for_render(self, cell_program): if self.vao_id is None: @@ -282,12 +239,11 @@ class CharGrid: if self.screen.scroll_changed or self.screen.is_dirty: self.update_cell_data(cell_program) sg = self.render_data - start, end = self.current_selection.limits(self.screen.scrolled_by, self.screen.lines, self.screen.columns) - sel = start, end, self.screen.scrolled_by + sel = self.screen.selection_limits() selection_changed = sel != self.last_rendered_selection if selection_changed: with cell_program.mapped_vertex_data(self.vao_id, self.selection_buffer_size, bufnum=1) as address: - self.screen.apply_selection(address, self.selection_buffer_size, *start, *end) + self.screen.apply_selection(address, self.selection_buffer_size) self.last_rendered_selection = sel return sg diff --git a/kitty/data-types.h b/kitty/data-types.h index 8557d03cc..2165466fe 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -220,11 +220,17 @@ typedef struct { #define PARSER_BUF_SZ (8 * 1024) #define READ_BUF_SZ (1024*1024) +typedef struct { + unsigned int start_x, start_y, start_scrolled_by, end_x, end_y, end_scrolled_by; + bool in_progress; +} Selection; + typedef struct { PyObject_HEAD unsigned int columns, lines, margin_top, margin_bottom, charset, scrolled_by; uint32_t utf8_state, utf8_codepoint, *g0_charset, *g1_charset, *g_charset; + Selection selection; bool use_latin1; Cursor *cursor; SavepointBuffer main_savepoints, alt_savepoints; diff --git a/kitty/screen.c b/kitty/screen.c index 3d9eaf616..c4e0bf08c 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -21,6 +21,7 @@ #include "wcwidth9.h" static const ScreenModes empty_modes = {0, .mDECAWM=true, .mDECTCEM=true, .mDECARM=true}; +static Selection EMPTY_SELECTION = {0}; // Constructor/destructor {{{ @@ -1225,6 +1226,7 @@ update_cell_data(Screen *self, PyObject *args) { if (!PyArg_ParseTuple(args, "O!p", &PyLong_Type, &dp, &force_screen_refresh)) return NULL; data = PyLong_AsVoidPtr(dp); PyObject *cursor_changed = self->cursor_changed; + bool selection_must_be_cleared = self->is_dirty == Py_True ? true : false; if (self->scrolled_by) self->scrolled_by = MIN(self->scrolled_by + history_line_added_count, self->historybuf->count); reset_dirty(self); self->scroll_changed = Py_False; @@ -1236,6 +1238,7 @@ update_cell_data(Screen *self, PyObject *args) { linebuf_init_line(self->linebuf, y - self->scrolled_by); update_line_data(self->linebuf->line, y, data); } + if (selection_must_be_cleared) self->selection = EMPTY_SELECTION; return Py_BuildValue("OO", cursor_changed, self->modes.mDECSCNM ? Py_True : Py_False); } @@ -1244,37 +1247,66 @@ is_selection_empty(Screen *self, unsigned int start_x, unsigned int start_y, uns return (start_x >= self->columns || start_y >= self->lines || end_x >= self->columns || end_y >= self->lines || (start_x == end_x && start_y == end_y)) ? true : false; } +typedef struct { + unsigned int x, y; +} SelectionBoundary; + +static inline void +selection_coord(Screen *self, unsigned int x, unsigned int y, unsigned int ydelta, SelectionBoundary *ans) { + if (y + self->scrolled_by < ydelta) { + ans->x = 0; ans->y = 0; + } else { + y = y - ydelta + self->scrolled_by; + if (y >= self->lines) { + ans->x = self->columns - 1; ans->y = self->lines - 1; + } else { + ans->x = x; ans->y = y; + } + } +} + +static inline void +selection_limits_(Screen *self, SelectionBoundary *left, SelectionBoundary *right) { + SelectionBoundary a, b; + selection_coord(self, self->selection.start_x, self->selection.start_y, self->selection.start_scrolled_by, &a); + selection_coord(self, self->selection.end_x, self->selection.end_y, self->selection.end_scrolled_by, &b); + if (a.y < b.y || (a.y == b.y && a.x <= b.x)) { *left = a; *right = b; } + else { *left = b; *right = a; } +} + static PyObject* apply_selection(Screen *self, PyObject *args) { - unsigned int size, start_x, end_x, start_y, end_y; + unsigned int size; PyObject *l; - if (!PyArg_ParseTuple(args, "O!IIIII", &PyLong_Type, &l, &size, &start_x, &start_y, &end_x, &end_y)) return NULL; + SelectionBoundary start, end; + if (!PyArg_ParseTuple(args, "O!I", &PyLong_Type, &l, &size)) return NULL; float *data = PyLong_AsVoidPtr(l); memset(data, 0, size); - if (is_selection_empty(self, start_x, start_y, end_x, end_y)) { Py_RETURN_NONE; } - for (index_type y = start_y; y <= end_y; y++) { + selection_limits_(self, &start, &end); + if (is_selection_empty(self, start.x, start.y, end.x, end.y)) { Py_RETURN_NONE; } + 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); + if (y == end.y) xlimit = MIN(end.x + 1, xlimit); float *line_start = data + self->columns * y; - for (index_type x = (y == start_y ? start_x : 0); x < xlimit; x++) line_start[x] = 1.0; + for (index_type x = (y == start.y ? start.x : 0); x < xlimit; x++) line_start[x] = 1.0; } Py_RETURN_NONE; } static PyObject* -text_for_selection(Screen *self, PyObject *args) { - unsigned int start_x, end_x, start_y, end_y; - if (!PyArg_ParseTuple(args, "IIII", &start_x, &start_y, &end_x, &end_y)) return NULL; - if (is_selection_empty(self, start_x, start_y, end_x, end_y)) return PyTuple_New(0); - Py_ssize_t i = 0, num_of_lines = end_y - start_y + 1; +text_for_selection(Screen *self) { + SelectionBoundary start, end; + selection_limits_(self, &start, &end); + if (is_selection_empty(self, start.x, start.y, end.x, end.y)) return PyTuple_New(0); + Py_ssize_t i = 0, num_of_lines = end.y - start.y + 1; PyObject *ans = PyTuple_New(num_of_lines); if (ans == NULL) return PyErr_NoMemory(); - for (index_type y = start_y; y <= end_y; y++, i++) { + 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); + 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(); } @@ -1357,8 +1389,48 @@ scroll(Screen *self, PyObject *args) { Py_RETURN_FALSE; } -static -PyObject* mark_as_dirty(Screen *self) { +static PyObject* +clear_selection(Screen *self) { + self->selection = EMPTY_SELECTION; + Py_RETURN_NONE; +} + +static PyObject* +is_selection_in_progress(Screen *self) { + PyObject *ans = self->selection.in_progress ? Py_True : Py_False; + Py_INCREF(ans); + return ans; +} + +static PyObject* +selection_limits(Screen *self) { + SelectionBoundary start, end; + selection_limits_(self, &start, &end); + return Py_BuildValue("(II)(II)", start.x, start.y, end.x, end.y); +} + +static PyObject* +start_selection(Screen *self, PyObject *args) { + unsigned int x, y; + if (!PyArg_ParseTuple(args, "II", &x, &y)) return NULL; +#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 + Py_RETURN_NONE; +} + +static PyObject* +update_selection(Screen *self, PyObject *args) { + unsigned int x, y; + int ended; + if (!PyArg_ParseTuple(args, "IIp", &x, &y, &ended)) return NULL; + self->selection.end_x = x; self->selection.end_y = y; self->selection.end_scrolled_by = self->scrolled_by; + if (ended) self->selection.in_progress = false; + Py_RETURN_NONE; +} + +static PyObject* +mark_as_dirty(Screen *self) { self->is_dirty = Py_True; Py_RETURN_NONE; } @@ -1444,7 +1516,12 @@ static PyMethodDef methods[] = { MND(apply_selection, METH_VARARGS) MND(selection_range_for_line, METH_VARARGS) MND(selection_range_for_word, METH_VARARGS) - MND(text_for_selection, METH_VARARGS) + MND(text_for_selection, METH_NOARGS) + MND(clear_selection, METH_NOARGS) + MND(is_selection_in_progress, METH_NOARGS) + MND(selection_limits, METH_NOARGS) + MND(start_selection, METH_VARARGS) + MND(update_selection, METH_VARARGS) MND(scroll, METH_VARARGS) MND(toggle_alt_screen, METH_NOARGS) MND(reset_callbacks, METH_NOARGS) diff --git a/kitty/window.py b/kitty/window.py index c8fe80f25..700ea42a6 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -243,7 +243,7 @@ class Window: if ev: self.write_to_child(ev) else: - if self.char_grid.current_selection.in_progress: + if self.screen.is_selection_in_progress(): self.char_grid.update_drag(None, x, y) margin = cell_size.height // 2 if y <= margin or y >= self.geometry.bottom - margin: