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):
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

View File

@ -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;

View File

@ -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)

View File

@ -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: