Move selection tracking into the Screen class

This commit is contained in:
Kovid Goyal 2017-09-10 13:56:22 +05:30
parent 5caea75115
commit 408c719c5f
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 117 additions and 78 deletions

View File

@ -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): 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 dx, dy = 2 * cell_width / viewport_width, 2 * cell_height / viewport_height
xmargin = window_geometry.left / viewport_width xmargin = window_geometry.left / viewport_width
@ -124,7 +95,6 @@ class CharGrid:
def __init__(self, screen, opts): def __init__(self, screen, opts):
self.vao_id = None self.vao_id = None
self.current_selection = Selection()
self.screen_reversed = False self.screen_reversed = False
self.last_rendered_selection = None self.last_rendered_selection = None
self.render_data = None self.render_data = None
@ -152,7 +122,7 @@ class CharGrid:
self.update_position(window_geometry) self.update_position(window_geometry)
self.data_buffer_size = self.screen_geometry.ynum * self.screen_geometry.xnum * CELL['size'] 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.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): def change_colors(self, changes):
dirtied = False dirtied = False
@ -175,13 +145,9 @@ class CharGrid:
def update_cell_data(self, cell_program): def update_cell_data(self, cell_program):
if self.data_buffer_size is None: if self.data_buffer_size is None:
return return
clear_selection = self.screen.is_dirty
with cell_program.mapped_vertex_data(self.vao_id, self.data_buffer_size) as address: 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( cursor_changed, self.screen_reversed = self.screen.update_cell_data(
address, False) address, False)
if clear_selection:
self.current_selection.clear()
self.render_data = self.screen_geometry self.render_data = self.screen_geometry
if cursor_changed: if cursor_changed:
c = self.screen.cursor c = self.screen.cursor
@ -200,16 +166,11 @@ class CharGrid:
y = 0 if my <= cell_size.height else self.screen.lines - 1 y = 0 if my <= cell_size.height else self.screen.lines - 1
ps = None ps = None
if is_press: if is_press:
self.current_selection.start_x = self.current_selection.end_x = x self.screen.start_selection(x, y)
self.current_selection.start_y = self.current_selection.end_y = y elif self.screen.is_selection_in_progress():
self.current_selection.start_scrolled_by = self.current_selection.end_scrolled_by = self.screen.scrolled_by ended = is_press is False
self.current_selection.in_progress = True self.screen.update_selection(x, y, ended)
elif self.current_selection.in_progress: if ended:
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
ps = self.text_for_selection() ps = self.text_for_selection()
if ps and ps.strip(): if ps and ps.strip():
set_primary_selection(ps) set_primary_selection(ps)
@ -251,16 +212,14 @@ class CharGrid:
if x is not None: if x is not None:
line = self.screen.visual_line(y) line = self.screen.visual_line(y)
if line is not None and count in (2, 3): 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: if count == 2:
s.start_x, xlimit = self.screen.selection_range_for_word(x, y, self.opts.select_by_word_characters) 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) end_x = max(start_x, xlimit - 1)
elif count == 3: elif count == 3:
s.start_x, xlimit = self.screen.selection_range_for_line(y) start_x, xlimit = self.screen.selection_range_for_line(y)
s.end_x = max(s.start_x, xlimit - 1) 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() ps = self.text_for_selection()
if ps: if ps:
set_primary_selection(ps) set_primary_selection(ps)
@ -271,10 +230,8 @@ class CharGrid:
self.screen.linebuf.as_ansi(ans.append) self.screen.linebuf.as_ansi(ans.append)
return ''.join(ans).encode('utf-8') return ''.join(ans).encode('utf-8')
def text_for_selection(self, sel=None): def text_for_selection(self):
s = sel or self.current_selection return ''.join(self.screen.text_for_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 prepare_for_render(self, cell_program): def prepare_for_render(self, cell_program):
if self.vao_id is None: if self.vao_id is None:
@ -282,12 +239,11 @@ class CharGrid:
if self.screen.scroll_changed or self.screen.is_dirty: if self.screen.scroll_changed or self.screen.is_dirty:
self.update_cell_data(cell_program) self.update_cell_data(cell_program)
sg = self.render_data sg = self.render_data
start, end = self.current_selection.limits(self.screen.scrolled_by, self.screen.lines, self.screen.columns) sel = self.screen.selection_limits()
sel = start, end, self.screen.scrolled_by
selection_changed = sel != self.last_rendered_selection selection_changed = sel != self.last_rendered_selection
if selection_changed: if selection_changed:
with cell_program.mapped_vertex_data(self.vao_id, self.selection_buffer_size, bufnum=1) as address: 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 self.last_rendered_selection = sel
return sg return sg

View File

@ -220,11 +220,17 @@ typedef struct {
#define PARSER_BUF_SZ (8 * 1024) #define PARSER_BUF_SZ (8 * 1024)
#define READ_BUF_SZ (1024*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 { typedef struct {
PyObject_HEAD PyObject_HEAD
unsigned int columns, lines, margin_top, margin_bottom, charset, scrolled_by; unsigned int columns, lines, margin_top, margin_bottom, charset, scrolled_by;
uint32_t utf8_state, utf8_codepoint, *g0_charset, *g1_charset, *g_charset; uint32_t utf8_state, utf8_codepoint, *g0_charset, *g1_charset, *g_charset;
Selection selection;
bool use_latin1; bool use_latin1;
Cursor *cursor; Cursor *cursor;
SavepointBuffer main_savepoints, alt_savepoints; SavepointBuffer main_savepoints, alt_savepoints;

View File

@ -21,6 +21,7 @@
#include "wcwidth9.h" #include "wcwidth9.h"
static const ScreenModes empty_modes = {0, .mDECAWM=true, .mDECTCEM=true, .mDECARM=true}; static const ScreenModes empty_modes = {0, .mDECAWM=true, .mDECTCEM=true, .mDECARM=true};
static Selection EMPTY_SELECTION = {0};
// Constructor/destructor {{{ // 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; if (!PyArg_ParseTuple(args, "O!p", &PyLong_Type, &dp, &force_screen_refresh)) return NULL;
data = PyLong_AsVoidPtr(dp); data = PyLong_AsVoidPtr(dp);
PyObject *cursor_changed = self->cursor_changed; 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); if (self->scrolled_by) self->scrolled_by = MIN(self->scrolled_by + history_line_added_count, self->historybuf->count);
reset_dirty(self); reset_dirty(self);
self->scroll_changed = Py_False; 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); linebuf_init_line(self->linebuf, y - self->scrolled_by);
update_line_data(self->linebuf->line, y, data); 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); 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; 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* static PyObject*
apply_selection(Screen *self, PyObject *args) { apply_selection(Screen *self, PyObject *args) {
unsigned int size, start_x, end_x, start_y, end_y; unsigned int size;
PyObject *l; 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); float *data = PyLong_AsVoidPtr(l);
memset(data, 0, size); memset(data, 0, size);
if (is_selection_empty(self, start_x, start_y, end_x, end_y)) { Py_RETURN_NONE; } selection_limits_(self, &start, &end);
for (index_type y = start_y; y <= end_y; y++) { 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); Line *line = visual_line_(self, y);
index_type xlimit = xlimit_for_line(line); 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; 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; Py_RETURN_NONE;
} }
static PyObject* static PyObject*
text_for_selection(Screen *self, PyObject *args) { text_for_selection(Screen *self) {
unsigned int start_x, end_x, start_y, end_y; SelectionBoundary start, end;
if (!PyArg_ParseTuple(args, "IIII", &start_x, &start_y, &end_x, &end_y)) return NULL; selection_limits_(self, &start, &end);
if (is_selection_empty(self, start_x, start_y, end_x, end_y)) return PyTuple_New(0); 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; Py_ssize_t i = 0, num_of_lines = end.y - start.y + 1;
PyObject *ans = PyTuple_New(num_of_lines); PyObject *ans = PyTuple_New(num_of_lines);
if (ans == NULL) return PyErr_NoMemory(); 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); Line *line = visual_line_(self, y);
index_type xlimit = xlimit_for_line(line); 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);
index_type xstart = (y == start_y ? start_x : 0); index_type xstart = (y == start.y ? start.x : 0);
char leading_char = i > 0 && !line->continued ? '\n' : 0; char leading_char = i > 0 && !line->continued ? '\n' : 0;
PyObject *text = unicode_in_range(line, xstart, xlimit, true, leading_char); PyObject *text = unicode_in_range(line, xstart, xlimit, true, leading_char);
if (text == NULL) { Py_DECREF(ans); return PyErr_NoMemory(); } if (text == NULL) { Py_DECREF(ans); return PyErr_NoMemory(); }
@ -1357,8 +1389,48 @@ scroll(Screen *self, PyObject *args) {
Py_RETURN_FALSE; Py_RETURN_FALSE;
} }
static static PyObject*
PyObject* mark_as_dirty(Screen *self) { 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; self->is_dirty = Py_True;
Py_RETURN_NONE; Py_RETURN_NONE;
} }
@ -1444,7 +1516,12 @@ static PyMethodDef methods[] = {
MND(apply_selection, METH_VARARGS) MND(apply_selection, METH_VARARGS)
MND(selection_range_for_line, METH_VARARGS) MND(selection_range_for_line, METH_VARARGS)
MND(selection_range_for_word, 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(scroll, METH_VARARGS)
MND(toggle_alt_screen, METH_NOARGS) MND(toggle_alt_screen, METH_NOARGS)
MND(reset_callbacks, METH_NOARGS) MND(reset_callbacks, METH_NOARGS)

View File

@ -243,7 +243,7 @@ class Window:
if ev: if ev:
self.write_to_child(ev) self.write_to_child(ev)
else: else:
if self.char_grid.current_selection.in_progress: if self.screen.is_selection_in_progress():
self.char_grid.update_drag(None, x, y) self.char_grid.update_drag(None, x, y)
margin = cell_size.height // 2 margin = cell_size.height // 2
if y <= margin or y >= self.geometry.bottom - margin: if y <= margin or y >= self.geometry.bottom - margin: