diff --git a/kitty/boss.py b/kitty/boss.py index baa1bcfba..e45ad8eef 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -407,9 +407,9 @@ class Boss: if '--program' not in cmdline: args.extend(('--program', self.opts.open_url_with)) if type_of_input in ('text', 'history'): - data = (w.buffer_as_text(add_history=type_of_input == 'history') + '\x1c').encode('utf-8') + data = ((w.buffer_as_text if type_of_input == 'history' else w.as_text)() + '\x1c').encode('utf-8') elif type_of_input in ('ansi', 'ansi-history'): - data = (w.buffer_as_ansi(add_history=type_of_input == 'ansi-history') + '\x1c').encode('utf-8') + data = ((w.buffer_as_ansi() if type_of_input == 'ansi-history' else w.as_text(as_ansi=True)) + '\x1c').encode('utf-8') elif type_of_input == 'none': data = None else: diff --git a/kitty/screen.c b/kitty/screen.c index 2baaabc09..19aa364dc 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -1452,6 +1452,41 @@ screen_open_url(Screen *self) { #define WRAP2(name, defval1, defval2) static PyObject* name(Screen *self, PyObject *args) { unsigned int a=defval1, b=defval2; if(!PyArg_ParseTuple(args, "|II", &a, &b)) return NULL; screen_##name(self, a, b); Py_RETURN_NONE; } #define WRAP2B(name) static PyObject* name(Screen *self, PyObject *args) { unsigned int a, b; int p; if(!PyArg_ParseTuple(args, "IIp", &a, &b, &p)) return NULL; screen_##name(self, a, b, (bool)p); Py_RETURN_NONE; } +static PyObject* +as_text(Screen *self, PyObject *args) { + PyObject *callback, *ret = NULL, *t = NULL; + Py_UCS4 *buf = NULL; + int as_ansi = 0; + if (!PyArg_ParseTuple(args, "O|p", &callback, &as_ansi)) return NULL; + PyObject *nl = PyUnicode_FromString("\n"); + if (nl == NULL) goto end; + if (as_ansi) { + buf = malloc(sizeof(Py_UCS4) * self->columns * 100); + if (buf == NULL) { PyErr_NoMemory(); goto end; } + } + for (index_type y = 0; y < self->lines; y++) { + Line *line = visual_line_(self, y); + if (!line->continued && y > 0) { + ret = PyObject_CallFunctionObjArgs(callback, nl, NULL); + if (ret == NULL) goto end; + Py_CLEAR(ret); + } + if (as_ansi) { + index_type num = line_as_ansi(line, buf, self->columns * 100 - 2); + t = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, num); + } else { + t = PyObject_Str((PyObject*)line); + } + if (t == NULL) goto end; + ret = PyObject_CallFunctionObjArgs(callback, t, NULL); + Py_DECREF(t); if (ret == NULL) goto end; Py_DECREF(ret); + } +end: + Py_CLEAR(nl); free(buf); + if (PyErr_Occurred()) return NULL; + Py_RETURN_NONE; +} + static PyObject* refresh_sprite_positions(Screen *self) { self->is_dirty = true; @@ -1825,6 +1860,7 @@ static PyMethodDef methods[] = { MND(cursor_down1, METH_VARARGS) MND(cursor_forward, METH_VARARGS) {"index", (PyCFunction)xxx_index, METH_VARARGS, ""}, + MND(as_text, METH_VARARGS) MND(refresh_sprite_positions, METH_NOARGS) MND(tab, METH_NOARGS) MND(backspace, METH_NOARGS) diff --git a/kitty/window.py b/kitty/window.py index c0553f7bf..23b5ab24e 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -330,6 +330,11 @@ class Window: ans = h + '\n' + ans return ans + def as_text(self, as_ansi=False): + lines = [] + self.screen.as_text(lines.append, as_ansi) + return ''.join(lines) + # actions {{{ def show_scrollback(self): diff --git a/kitty_tests/screen.py b/kitty_tests/screen.py index 8437ddcd4..787d94021 100644 --- a/kitty_tests/screen.py +++ b/kitty_tests/screen.py @@ -388,3 +388,17 @@ class TestScreen(BaseTest): self.ae(s.text_for_selection(), expected) s.scroll(2, True) self.ae(s.text_for_selection(), expected) + + def test_serialize(self): + s = self.create_screen() + s.draw('ab' * s.columns) + s.carriage_return(), s.linefeed() + s.draw('c') + + def as_text(as_ansi=False): + d = [] + s.as_text(d.append, as_ansi) + return ''.join(d) + + self.ae(as_text(), 'ababababab\nc\n\n') + self.ae(as_text(True), '\x1b[0mababa\x1b[0mbabab\n\x1b[0mc\n\n')