Move selection tracking into the Screen class
This commit is contained in:
parent
5caea75115
commit
408c719c5f
@ -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
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
111
kitty/screen.c
111
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)
|
||||
|
||||
@ -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:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user