mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-09 23:54:20 +02:00
Make selection smarter
It now does not add trailing blank cells on selected lines to the selection.
This commit is contained in:
@@ -90,37 +90,6 @@ class Selection: # {{{
|
||||
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 text(self, linebuf, historybuf):
|
||||
sy = self.start_y - self.start_scrolled_by
|
||||
ey = self.end_y - self.end_scrolled_by
|
||||
if sy == ey and self.start_x == self.end_x:
|
||||
return ''
|
||||
a, b = (sy, self.start_x), (ey, self.end_x)
|
||||
if a > b:
|
||||
a, b = b, a
|
||||
|
||||
def line(y):
|
||||
if y < 0:
|
||||
return historybuf.line(-1 - y)
|
||||
return linebuf.line(y)
|
||||
|
||||
lines = []
|
||||
for y in range(a[0], b[0] + 1):
|
||||
startx, endx = 0, linebuf.xnum - 1
|
||||
if y == a[0]:
|
||||
startx = max(0, min(a[1], endx))
|
||||
if y == b[0]:
|
||||
endx = max(0, min(b[1], endx))
|
||||
l = line(y)
|
||||
is_continued = l.is_continued()
|
||||
if endx - startx >= linebuf.xnum - 1:
|
||||
l = str(l).rstrip(' ')
|
||||
else:
|
||||
l = ''.join(l[x] for x in range(startx, endx + 1))
|
||||
if not is_continued and startx == 0 and len(lines) > 0:
|
||||
l = '\n' + l
|
||||
lines.append(l)
|
||||
return ''.join(lines)
|
||||
# }}}
|
||||
|
||||
|
||||
@@ -312,21 +281,7 @@ class CharGrid:
|
||||
s.start_scrolled_by = s.end_scrolled_by = self.scrolled_by
|
||||
s.start_y = s.end_y = y
|
||||
s.in_progress = False
|
||||
if count == 3:
|
||||
for i in range(self.screen.columns):
|
||||
if line[i] != ' ':
|
||||
s.start_x = i
|
||||
break
|
||||
else:
|
||||
s.start_x = 0
|
||||
for i in range(self.screen.columns):
|
||||
c = self.screen.columns - 1 - i
|
||||
if line[c] != ' ':
|
||||
s.end_x = c
|
||||
break
|
||||
else:
|
||||
s.end_x = self.screen.columns - 1
|
||||
elif count == 2:
|
||||
if count == 2:
|
||||
i = x
|
||||
while i >= 0 and self.word_pat.match(line[i]) is not None:
|
||||
i -= 1
|
||||
@@ -335,6 +290,9 @@ class CharGrid:
|
||||
while i < self.screen.columns and self.word_pat.match(line[i]) is not None:
|
||||
i += 1
|
||||
s.end_x = i if i == x else i - 1
|
||||
elif count == 3:
|
||||
s.start_x, xlimit = self.screen.selection_range_for_line(y, self.scrolled_by)
|
||||
s.end_x = max(s.start_x, xlimit - 1)
|
||||
ps = self.text_for_selection()
|
||||
if ps:
|
||||
set_primary_selection(ps)
|
||||
@@ -347,7 +305,8 @@ class CharGrid:
|
||||
|
||||
def text_for_selection(self, sel=None):
|
||||
s = sel or self.current_selection
|
||||
return s.text(self.screen.linebuf, self.screen.historybuf)
|
||||
start, end = s.limits(self.scrolled_by, self.screen.lines, self.screen.columns)
|
||||
return ''.join(self.screen.text_for_selection(self.scrolled_by, *start, *end))
|
||||
|
||||
def prepare_for_render(self, cell_program):
|
||||
if self.vao_id is None:
|
||||
@@ -356,11 +315,12 @@ class CharGrid:
|
||||
self.update_cell_data(cell_program)
|
||||
self.scroll_changed = False
|
||||
sg = self.render_data
|
||||
start, end = sel = self.current_selection.limits(self.scrolled_by, self.screen.lines, self.screen.columns)
|
||||
start, end = self.current_selection.limits(self.scrolled_by, self.screen.lines, self.screen.columns)
|
||||
sel = start, end, self.scrolled_by
|
||||
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, start[0], start[1], end[0], end[1], self.selection_buffer_size)
|
||||
self.screen.apply_selection(address, self.selection_buffer_size, self.scrolled_by, *start, *end)
|
||||
self.last_rendered_selection = sel
|
||||
return sg
|
||||
|
||||
|
||||
29
kitty/line.c
29
kitty/line.c
@@ -58,31 +58,38 @@ text_at(Line* self, Py_ssize_t xval) {
|
||||
return line_text_at(self->cells[xval].ch & CHAR_MASK, self->cells[xval].cc);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
as_unicode(Line* self) {
|
||||
Py_ssize_t n = 0;
|
||||
PyObject*
|
||||
unicode_in_range(Line *self, index_type start, index_type limit, bool include_cc, char leading_char) {
|
||||
size_t n = 0;
|
||||
static Py_UCS4 buf[4096];
|
||||
index_type xlimit = MIN(sizeof(buf)/sizeof(buf[0]), xlimit_for_line(self));
|
||||
if (leading_char) buf[n++] = leading_char;
|
||||
char_type previous_width = 0;
|
||||
for(index_type i = 0; i < xlimit; i++) {
|
||||
for(index_type i = start; i < limit && n < sizeof(buf)/sizeof(buf[0]) - 4; i++) {
|
||||
char_type ch = self->cells[i].ch & CHAR_MASK;
|
||||
if (ch == 0) {
|
||||
if (previous_width == 2) { previous_width = 0; continue; };
|
||||
ch = ' ';
|
||||
}
|
||||
buf[n++] = ch;
|
||||
char_type cc = self->cells[i].cc;
|
||||
Py_UCS4 cc1 = cc & CC_MASK, cc2;
|
||||
if (cc1) {
|
||||
buf[n++] = cc1;
|
||||
cc2 = cc >> 16;
|
||||
if (cc2) buf[n++] = cc2;
|
||||
if (include_cc) {
|
||||
char_type cc = self->cells[i].cc;
|
||||
Py_UCS4 cc1 = cc & CC_MASK, cc2;
|
||||
if (cc1) {
|
||||
buf[n++] = cc1;
|
||||
cc2 = cc >> 16;
|
||||
if (cc2) buf[n++] = cc2;
|
||||
}
|
||||
}
|
||||
previous_width = (self->cells[i].ch >> ATTRS_SHIFT) & WIDTH_MASK;
|
||||
}
|
||||
return PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, n);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
as_unicode(Line* self) {
|
||||
return unicode_in_range(self, 0, xlimit_for_line(self), true, 0);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
write_sgr(unsigned int val, Py_UCS4 *buf, index_type buflen, index_type *i) {
|
||||
static char s[20] = {0};
|
||||
|
||||
@@ -50,10 +50,7 @@ static inline index_type
|
||||
xlimit_for_line(Line *line) {
|
||||
index_type xlimit = line->xnum;
|
||||
if (BLANK_CHAR == 0) {
|
||||
while (xlimit != 0) {
|
||||
if ((line->cells[xlimit - 1].ch & CHAR_MASK) != BLANK_CHAR) break;
|
||||
xlimit--;
|
||||
}
|
||||
while (xlimit > 0 && (line->cells[xlimit - 1].ch & CHAR_MASK) == BLANK_CHAR) xlimit--;
|
||||
}
|
||||
return xlimit;
|
||||
}
|
||||
@@ -66,6 +63,7 @@ void line_right_shift(Line *, unsigned int , unsigned int );
|
||||
void line_add_combining_char(Line *, uint32_t , unsigned int );
|
||||
index_type line_as_ansi(Line *self, Py_UCS4 *buf, index_type buflen);
|
||||
unsigned int line_length(Line *self);
|
||||
PyObject* unicode_in_range(Line *self, index_type start, index_type limit, bool include_cc, char leading_char);
|
||||
|
||||
void linebuf_init_line(LineBuf *, index_type);
|
||||
void linebuf_clear(LineBuf *, char_type ch);
|
||||
|
||||
@@ -1078,23 +1078,26 @@ line(Screen *self, PyObject *val) {
|
||||
return (PyObject*) self->linebuf->line;
|
||||
}
|
||||
|
||||
static inline Line*
|
||||
visual_line_(Screen *self, index_type y, index_type scrolled_by) {
|
||||
if (scrolled_by) {
|
||||
if (y < scrolled_by) {
|
||||
historybuf_init_line(self->historybuf, scrolled_by - 1 - y, self->historybuf->line);
|
||||
return self->historybuf->line;
|
||||
}
|
||||
y -= scrolled_by;
|
||||
}
|
||||
linebuf_init_line(self->linebuf, y);
|
||||
return self->linebuf->line;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
visual_line(Screen *self, PyObject *args) {
|
||||
// The line corresponding to the yth visual line, taking into account scrolling
|
||||
unsigned int y, scrolled_by;
|
||||
if (!PyArg_ParseTuple(args, "II", &y, &scrolled_by)) return NULL;
|
||||
if (y >= self->lines) { Py_RETURN_NONE; }
|
||||
if (scrolled_by) {
|
||||
if (y < scrolled_by) {
|
||||
historybuf_init_line(self->historybuf, scrolled_by - 1 - y, self->historybuf->line);
|
||||
Py_INCREF(self->historybuf->line);
|
||||
return (PyObject*) self->historybuf->line;
|
||||
}
|
||||
y -= scrolled_by;
|
||||
}
|
||||
linebuf_init_line(self->linebuf, y);
|
||||
Py_INCREF(self->linebuf->line);
|
||||
return (PyObject*) self->linebuf->line;
|
||||
return Py_BuildValue("O", visual_line_(self, y, scrolled_by));
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
@@ -1229,20 +1232,63 @@ update_cell_data(Screen *self, PyObject *args) {
|
||||
return Py_BuildValue("OIO", cursor_changed, scrolled_by, self->modes.mDECSCNM ? Py_True : Py_False);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
is_selection_empty(Screen *self, unsigned int start_x, unsigned int start_y, unsigned int end_x, unsigned int end_y) {
|
||||
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;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
apply_selection(Screen *self, PyObject *args) {
|
||||
unsigned int size, startx, endx, starty, endy, i, end;
|
||||
unsigned int size, start_x, end_x, start_y, end_y, scrolled_by;
|
||||
PyObject *l;
|
||||
if (!PyArg_ParseTuple(args, "O!IIIII", &PyLong_Type, &l, &startx, &starty, &endx, &endy, &size)) return NULL;
|
||||
if (startx >= self->columns || starty >= self->lines || endx >= self->columns || endy >= self->lines) { Py_RETURN_NONE; }
|
||||
if (!PyArg_ParseTuple(args, "O!IIIIII", &PyLong_Type, &l, &size, &scrolled_by, &start_x, &start_y, &end_x, &end_y)) return NULL;
|
||||
float *data = PyLong_AsVoidPtr(l);
|
||||
memset(data, 0, size);
|
||||
end = endy * self->columns + endx;
|
||||
i = starty * self->columns + startx;
|
||||
if (i != end) { for(; i <= end; i++) data[i] = 1; }
|
||||
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, scrolled_by);
|
||||
index_type xlimit = xlimit_for_line(line);
|
||||
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;
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
text_for_selection(Screen *self, PyObject *args) {
|
||||
unsigned int start_x, end_x, start_y, end_y, scrolled_by;
|
||||
if (!PyArg_ParseTuple(args, "IIIII", &scrolled_by, &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;
|
||||
PyObject *ans = PyTuple_New(num_of_lines);
|
||||
if (ans == NULL) return PyErr_NoMemory();
|
||||
for (index_type y = start_y; y <= end_y; y++, i++) {
|
||||
Line *line = visual_line_(self, y, scrolled_by);
|
||||
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);
|
||||
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(); }
|
||||
PyTuple_SET_ITEM(ans, i, text);
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
selection_range_for_line(Screen *self, PyObject *args) {
|
||||
unsigned int y, scrolled_by;
|
||||
if (!PyArg_ParseTuple(args, "II", &y, &scrolled_by)) return NULL;
|
||||
if (y >= self->lines) { PyErr_SetString(PyExc_ValueError, "y larger than lines"); return NULL; }
|
||||
Line *line = visual_line_(self, y, scrolled_by);
|
||||
index_type xlimit = line->xnum, xstart = 0;
|
||||
while (xlimit > 0 && CHAR_IS_BLANK(line->cells[xlimit - 1].ch)) xlimit--;
|
||||
while (xstart < xlimit && CHAR_IS_BLANK(line->cells[xstart].ch)) xstart++;
|
||||
return Py_BuildValue("II", (unsigned int)xstart, (unsigned int)xlimit);
|
||||
}
|
||||
|
||||
|
||||
static
|
||||
PyObject* mark_as_dirty(Screen *self) {
|
||||
self->is_dirty = Py_True;
|
||||
@@ -1328,6 +1374,8 @@ static PyMethodDef methods[] = {
|
||||
MND(resize, METH_VARARGS)
|
||||
MND(set_margins, METH_VARARGS)
|
||||
MND(apply_selection, METH_VARARGS)
|
||||
MND(selection_range_for_line, METH_VARARGS)
|
||||
MND(text_for_selection, METH_VARARGS)
|
||||
MND(toggle_alt_screen, METH_NOARGS)
|
||||
MND(reset_callbacks, METH_NOARGS)
|
||||
{"update_cell_data", (PyCFunction)update_cell_data, METH_VARARGS, ""},
|
||||
|
||||
Reference in New Issue
Block a user