diff --git a/kitty/char_grid.py b/kitty/char_grid.py index b8c77d7ca..d8721bc77 100644 --- a/kitty/char_grid.py +++ b/kitty/char_grid.py @@ -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 diff --git a/kitty/line.c b/kitty/line.c index 14c48c4bd..1d396c7ed 100644 --- a/kitty/line.c +++ b/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}; diff --git a/kitty/lineops.h b/kitty/lineops.h index 4c40110d1..e7e277179 100644 --- a/kitty/lineops.h +++ b/kitty/lineops.h @@ -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); diff --git a/kitty/screen.c b/kitty/screen.c index 37abe4261..9b83834a0 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -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, ""},