From ac1aaab077566fb1493cd44cfede5c82c9ae0f77 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 10 Jan 2025 19:44:38 +0530 Subject: [PATCH] More work of selections as text --- kitty/screen.c | 66 ++++++++++++++++++++++++++++------------ kitty_tests/multicell.py | 9 ++++++ 2 files changed, 55 insertions(+), 20 deletions(-) diff --git a/kitty/screen.c b/kitty/screen.c index 2378f59cc..578a0dbbf 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -3479,7 +3479,9 @@ text_for_range(Screen *self, const Selection *sel, bool insert_newlines, bool st const int limit = MIN((int)self->lines, idata.y_limit); if (idata.y >= limit) return PyTuple_New(0); RAII_PyObject(ans, PyTuple_New(limit - idata.y)); - if (!ans) return NULL; + RAII_PyObject(nl, PyUnicode_FromString("\n")); + RAII_PyObject(empty, PyUnicode_FromString("")); + if (!ans || !nl || !empty) return NULL; range_line(self, idata.y, &next_line); xrn = xrange_for_iteration_with_multicells(&idata, idata.y, &next_line); for (int i = 0, y = idata.y; y < limit; y++, i++) { @@ -3492,7 +3494,7 @@ text_for_range(Screen *self, const Selection *sel, bool insert_newlines, bool st index_type new_limit = limit_without_trailing_whitespace(&line, x_limit); if (new_limit != x_limit) { x_limit = new_limit; - is_only_whitespace_line = true; + is_only_whitespace_line = new_limit <= xr.x; } } const bool add_trailing_newline = insert_newlines && !is_last_line; @@ -3510,7 +3512,8 @@ text_for_range(Screen *self, const Selection *sel, bool insert_newlines, bool st } PyObject *text = NULL; if (x_limit <= xr.x && is_only_whitespace_line) { // we want a newline on only whitespace lines even if they are continued - text = add_trailing_newline ? PyUnicode_FromString("\n") : PyUnicode_FromString(""); + text = add_trailing_newline ? nl : empty; + text = Py_NewRef(text); } else { if (!unicode_in_range(&line, xr.x, x_limit, true, add_trailing_newline, false, !is_first_line, &self->as_ansi_buf)) return PyErr_NoMemory(); if (!is_last_line) { @@ -3539,40 +3542,63 @@ ansi_for_range(Screen *self, const Selection *sel, bool insert_newlines, bool st RAII_PyObject(ans, PyTuple_New(limit - idata.y + 1)); RAII_PyObject(nl, PyUnicode_FromString("\n")); if (!ans || !nl) return NULL; - ANSIBuf output = {0}; bool has_escape_codes = false; bool need_newline = false; - ANSILineState s = {.output_buf=&output}; + ANSILineState s = {.output_buf=&self->as_ansi_buf}; + Line line = {.xnum=self->columns, .text_cache=self->text_cache}, next_line = line; XRange xr, xrn = {0}; CPUCell *c; + range_line(self, idata.y, &next_line); + xrn = xrange_for_iteration_with_multicells(&idata, idata.y, &next_line); + s.output_buf->active_hyperlink_id = 0; s.output_buf->len = 0; for (int i = 0, y = idata.y; y < limit; y++, i++) { - Line *line = range_line_(self, y); - XRange xr = xrange_for_iteration_with_multicells(&idata, y, line); - output.len = 0; + const bool is_first_line = y == idata.y, is_last_line = y + 1 >= limit; + xr = xrn; + line = next_line; + s.output_buf->len = 0; char_type prefix_char = need_newline ? '\n' : 0; index_type x_limit = xr.x_limit; + bool is_only_whitespace_line = false; if (strip_trailing_whitespace) { - index_type new_limit = limit_without_trailing_whitespace(line, x_limit); + index_type new_limit = limit_without_trailing_whitespace(&line, x_limit); if (new_limit != x_limit) { x_limit = new_limit; - if (!x_limit) { - PyTuple_SET_ITEM(ans, i, nl); - continue; + is_only_whitespace_line = new_limit <= xr.x; + } + } + if (!is_last_line) { + range_line(self, y + 1, &next_line); + xrn = xrange_for_iteration_with_multicells(&idata, y+1, &next_line); + for (index_type x = 0; x < xr.x; x++) { + if ((c = &line.cpu_cells[x])->is_multicell && c->scale > 1 && c->y + 1 < c->scale && x <= xrn.x) { + index_type mc_limit = x + mcd_x_limit(c); + if (line_as_ansi(&line, &s, x, mc_limit, prefix_char, false)) has_escape_codes = true; + x = mc_limit - 1; + prefix_char = 0; } } } - const bool is_first_line = y == idata.y; - if (is_first_line) { + if (x_limit <= xr.x && is_only_whitespace_line) { // we want a newline on only whitespace lines even if they are continued + if (insert_newlines) need_newline = true; + } else { + if (line_as_ansi(&line, &s, xr.x, x_limit, prefix_char, !is_first_line)) has_escape_codes = true; + need_newline = insert_newlines && !line.cpu_cells[line.xnum-1].next_char_was_wrapped; + if (!is_last_line) { + for (index_type x = x_limit; x < MIN(line.xnum, xrn.x_limit); x++) { + if ((c = &line.cpu_cells[x])->is_multicell && c->scale > 1 && c->y + 1 < c->scale) { + index_type mc_limit = x + mcd_x_limit(c); + if (line_as_ansi(&line, &s, x, mc_limit, 0, false)) has_escape_codes = true; + x = mc_limit - 1; + } + } + } } - if (line_as_ansi(line, &s, xr.x, x_limit, prefix_char, !is_first_line)) has_escape_codes = true; - need_newline = insert_newlines && !line->cpu_cells[line->xnum-1].next_char_was_wrapped; - PyObject *t = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, output.buf, output.len); + PyObject *t = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, s.output_buf->buf, s.output_buf->len); if (!t) return NULL; PyTuple_SET_ITEM(ans, i, t); } - PyObject *t = PyUnicode_FromFormat("%s%s", has_escape_codes ? "\x1b[m" : "", output.active_hyperlink_id ? "\x1b]8;;\x1b\\" : ""); + PyObject *t = PyUnicode_FromFormat("%s%s", has_escape_codes ? "\x1b[m" : "", s.output_buf->active_hyperlink_id ? "\x1b]8;;\x1b\\" : ""); if (!t) return NULL; PyTuple_SET_ITEM(ans, PyTuple_GET_SIZE(ans) - 1, t); - Py_INCREF(ans); - return ans; + return Py_NewRef(ans); } diff --git a/kitty_tests/multicell.py b/kitty_tests/multicell.py index 9c0c80b92..f3bd77019 100644 --- a/kitty_tests/multicell.py +++ b/kitty_tests/multicell.py @@ -618,18 +618,27 @@ def test_multicell(self: TestMulticell) -> None: for i, (e, a) in enumerate(zip(as_lists(bytes(expected)), as_lists(actual))): self.ae(e, a, f'Row: {i}') + def ast(*expected, as_ansi=False, strip_trailing_whitespace=False): + actual = s.text_for_selection(as_ansi, strip_trailing_whitespace) + self.ae(expected, actual) + s.reset() s.draw('a'), multicell(s, 'b', width=2), s.draw('c') ss(p(), p(x=1, in_left_half_of_cell=False)) asl((0, 0, 2)) + ast('ab') ss(p(x=2), p(x=3, in_left_half_of_cell=False)) asl((0, 1, 3)) + ast('bc') s.reset() s.draw('a'), multicell(s, 'b', scale=2), s.draw('c'), multicell(s, 'd', scale=2) ss(p(), p(x=4, in_left_half_of_cell=False)) asl((0, 0, 5), (1, 1, 2), (1, 4, 5)) + ast('abcd') ss(p(y=1, x=1), p(y=1, x=1, in_left_half_of_cell=False)) asl((0, 1, 2), (1, 1, 2)) + ast('b') ss(p(y=1, x=0), p(y=1, x=1, in_left_half_of_cell=False)) # empty leading cell before multiline on y=1 asl((0, 1, 2), (1, 0, 2)) + ast(' b')