diff --git a/kitty/boss.py b/kitty/boss.py index ed2419fb8..a9b9b2ac7 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -406,10 +406,8 @@ class Boss: args = shlex.split(cmdline) if cmdline else [] 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 if type_of_input == 'history' else w.as_text)().encode('utf-8') - elif type_of_input in ('ansi', 'ansi-history'): - data = (w.buffer_as_ansi() if type_of_input == 'ansi-history' else w.as_text(as_ansi=True)).encode('utf-8') + if type_of_input in ('text', 'history', 'ansi', 'ansi-history'): + data = w.as_text(as_ansi='ansi' in type_of_input, add_history='history' in type_of_input).encode('utf-8') elif type_of_input == 'none': data = None else: @@ -494,9 +492,13 @@ class Boss: if arg == '@selection': return w.text_for_selection() if arg == '@ansi': - return w.buffer_as_ansi() + return w.as_text(as_ansi=True, add_history=True) if arg == '@text': - return w.buffer_as_text() + return w.as_text(add_history=True) + if arg == '@screen': + return w.as_text() + if arg == '@ansi_screen': + return w.as_text(as_ansi=True) if args[0].startswith('@'): stdin = data_for_at(args[0]) or None diff --git a/kitty/history.c b/kitty/history.c index bd8bb0569..a6129e6a7 100644 --- a/kitty/history.c +++ b/kitty/history.c @@ -202,6 +202,18 @@ as_ansi(HistoryBuf *self, PyObject *callback) { Py_RETURN_NONE; } +static inline Line* +get_line(HistoryBuf *self, index_type y, Line *l) { init_line(self, y, l); return l; } + +static PyObject* +as_text(HistoryBuf *self, PyObject *args) { + Line l = {.xnum=self->xnum}; +#define gl(self, y) get_line(self, y, &l); + as_text_generic(args, self, gl, self->count, self->xnum, callback, as_ansi); +#undef gl +} + + static PyObject* dirty_lines(HistoryBuf *self) { #define dirty_lines_doc "dirty_lines() -> Line numbers of all lines that have dirty text." @@ -223,6 +235,7 @@ static PyMethodDef methods[] = { METHOD(change_num_of_lines, METH_O) METHOD(line, METH_O) METHOD(as_ansi, METH_O) + METHODB(as_text, METH_VARARGS), METHOD(dirty_lines, METH_NOARGS) METHOD(push, METH_VARARGS) METHOD(rewrap, METH_VARARGS) diff --git a/kitty/kitty.conf b/kitty/kitty.conf index de561a133..90baa2035 100644 --- a/kitty/kitty.conf +++ b/kitty/kitty.conf @@ -298,7 +298,8 @@ map ctrl+shift+0 tenth_window # map ctrl+shift+y new_window less @selection # # You can even send the contents of the current screen + history buffer as stdin using -# the placeholders @text (which is the plain text) and @ansi (which includes text styling escape codes) +# the placeholders @text (which is the plain text) and @ansi (which includes text styling escape codes). +# For only the current screen, use @screen or @ansi_screen. # For example, the following command opens the scrollback buffer in less in a new window. # map ctrl+shift+y new_window @ansi less +G -R # diff --git a/kitty/line-buf.c b/kitty/line-buf.c index 7a881e16e..e5dee70e4 100644 --- a/kitty/line-buf.c +++ b/kitty/line-buf.c @@ -406,6 +406,18 @@ as_ansi(LineBuf *self, PyObject *callback) { Py_RETURN_NONE; } +static inline Line* +get_line(LineBuf *self, index_type y) { + linebuf_init_line(self, y); + return self->line; +} + +static PyObject* +as_text(LineBuf *self, PyObject *args) { + as_text_generic(args, self, get_line, self->ynum, self->xnum, callback, as_ansi); +} + + static PyObject* __str__(LineBuf *self) { PyObject *lines = PyTuple_New(self->ynum); @@ -440,6 +452,7 @@ static PyMethodDef methods[] = { METHOD(rewrap, METH_VARARGS) METHOD(clear, METH_NOARGS) METHOD(as_ansi, METH_O) + METHODB(as_text, METH_VARARGS), METHOD(set_attribute, METH_VARARGS) METHOD(set_continued, METH_VARARGS) METHOD(dirty_lines, METH_NOARGS) diff --git a/kitty/lineops.h b/kitty/lineops.h index 520e9d252..4d18b7528 100644 --- a/kitty/lineops.h +++ b/kitty/lineops.h @@ -44,6 +44,7 @@ xlimit_for_line(Line *line) { return xlimit; } + void line_clear_text(Line *self, unsigned int at, unsigned int num, char_type ch); void line_apply_cursor(Line *self, Cursor *cursor, unsigned int at, unsigned int num, bool clear_char); void line_set_char(Line *, unsigned int , uint32_t , unsigned int , Cursor *, bool); @@ -79,3 +80,39 @@ void historybuf_mark_line_clean(HistoryBuf *self, index_type y); void historybuf_mark_line_dirty(HistoryBuf *self, index_type y); void historybuf_refresh_sprite_positions(HistoryBuf *self); void historybuf_clear(HistoryBuf *self); + + +#define as_text_generic(args, container, get_line, lines, columns, callback, as_ansi) { \ + PyObject *callback; \ + int as_ansi = 0; \ + if (!PyArg_ParseTuple(args, "O|p", &callback, &as_ansi)) return NULL; \ + PyObject *ret = NULL, *t = NULL; \ + Py_UCS4 *buf = NULL; \ + PyObject *nl = PyUnicode_FromString("\n"); \ + if (nl == NULL) goto end; \ + if (as_ansi) { \ + buf = malloc(sizeof(Py_UCS4) * columns * 100); \ + if (buf == NULL) { PyErr_NoMemory(); goto end; } \ + } \ + for (index_type y = 0; y < lines; y++) { \ + Line *line = get_line(container, 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, 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; \ +} diff --git a/kitty/remote_control.py b/kitty/remote_control.py index 33e86e8d8..4cce23873 100644 --- a/kitty/remote_control.py +++ b/kitty/remote_control.py @@ -387,8 +387,7 @@ def get_text(boss, window, payload): if payload['extent'] == 'selection': ans = window.text_for_selection() else: - f = window.buffer_as_ansi if payload['ansi'] else window.buffer_as_text - ans = f(add_history=payload['extent'] == 'all') + ans = window.as_text(as_ansi=bool(payload['ansi']), add_history=True) return ans diff --git a/kitty/screen.c b/kitty/screen.c index 19aa364dc..ae2b3bf20 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -1454,37 +1454,7 @@ screen_open_url(Screen *self) { 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; + as_text_generic(args, self, visual_line_, self->lines, self->columns, callback, as_ansi); } static PyObject* diff --git a/kitty/window.py b/kitty/window.py index 23b5ab24e..944360cbc 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -315,30 +315,19 @@ class Window: self.screen.reset_callbacks() self.screen = None - def buffer_as_ansi(self, add_history=True): - data = [] - if add_history: - self.screen.historybuf.as_ansi(data.append) - self.screen.linebuf.as_ansi(data.append) - return ''.join(data) - - def buffer_as_text(self, add_history=True): - ans = str(self.screen.linebuf).rstrip('\n') - if add_history: - h = str(self.screen.historybuf) - if h.strip(): - ans = h + '\n' + ans - return ans - - def as_text(self, as_ansi=False): + def as_text(self, as_ansi=False, add_history=False): lines = [] self.screen.as_text(lines.append, as_ansi) + if add_history: + h = [] + self.screen.historybuf.as_text(h.append, as_ansi) + lines = h + lines return ''.join(lines) # actions {{{ def show_scrollback(self): - get_boss().display_scrollback(self, self.buffer_as_ansi().encode('utf-8')) + get_boss().display_scrollback(self, self.as_text(as_ansi=True, add_history=True).encode('utf-8')) def paste(self, text): if text and not self.destroyed: