Remove the max size limit for line_as_ansi

Needed for output of hyperlinks, also more efficient, since avoids
malloc per line. Also fix pagerhist not having SGR reset at the start of
every line.
This commit is contained in:
Kovid Goyal 2020-09-20 10:50:37 +05:30
parent 581126c748
commit a78515e5bf
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
10 changed files with 174 additions and 141 deletions

View File

@ -200,6 +200,11 @@ typedef struct {
bool rewrap_needed;
} PagerHistoryBuf;
typedef struct {
Py_UCS4 *buf;
size_t len, capacity;
} ANSIBuf;
typedef struct {
PyObject_HEAD

View File

@ -93,19 +93,13 @@ pagerhist_clear(HistoryBuf *self) {
self->pagerhist = alloc_pagerhist(pagerhist_sz);
}
static PyObject *
new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
HistoryBuf *self;
unsigned int xnum = 1, ynum = 1, pagerhist_sz = 0;
if (!PyArg_ParseTuple(args, "II|I", &ynum, &xnum, &pagerhist_sz)) return NULL;
static HistoryBuf*
create_historybuf(PyTypeObject *type, unsigned int xnum, unsigned int ynum, unsigned int pagerhist_sz) {
if (xnum == 0 || ynum == 0) {
PyErr_SetString(PyExc_ValueError, "Cannot create an empty history buffer");
return NULL;
}
self = (HistoryBuf *)type->tp_alloc(type, 0);
HistoryBuf *self = (HistoryBuf *)type->tp_alloc(type, 0);
if (self != NULL) {
self->xnum = xnum;
self->ynum = ynum;
@ -115,8 +109,15 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
self->line->xnum = xnum;
self->pagerhist = alloc_pagerhist(pagerhist_sz);
}
return self;
}
return (PyObject*)self;
static PyObject *
new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
unsigned int xnum = 1, ynum = 1, pagerhist_sz = 0;
if (!PyArg_ParseTuple(args, "II|I", &ynum, &xnum, &pagerhist_sz)) return NULL;
HistoryBuf *ans = create_historybuf(type, xnum, ynum, pagerhist_sz);
return (PyObject*)ans;
}
static void
@ -179,57 +180,49 @@ historybuf_clear(HistoryBuf *self) {
self->start_of_data = 0;
}
static inline bool
pagerhist_write(PagerHistoryBuf *ph, const Py_UCS4 *buf, size_t sz) {
if (sz > ph->bufsize) return false;
if (!sz) return true;
if (ph->bufsize - ph->end < sz && !pagerhist_extend(ph, sz)) {
ph->bufend = ph->end; ph->end = 0;
}
memcpy(ph->buffer + ph->end, buf, sz * sizeof(Py_UCS4));
ph->end += sz;
if (ph->bufend) {
ph->start = ph->end + 1 < ph->bufend ? ph->end + 1 : 0;
}
return true;
}
static inline void
pagerhist_push(HistoryBuf *self) {
pagerhist_push(HistoryBuf *self, ANSIBuf *as_ansi_buf) {
PagerHistoryBuf *ph = self->pagerhist;
if (!ph) return;
bool truncated;
const GPUCell *prev_cell = NULL;
Line l = {.xnum=self->xnum};
init_line(self, self->start_of_data, &l);
#define EXPAND_IF_FULL(sz) { \
if (ph->bufsize - ph->end < sz && !pagerhist_extend(ph, sz)) { \
ph->bufend = ph->end; ph->end = 0; \
} \
}
size_t sz = MAX(1024u, ph->bufsize - ph->end);
sz = MAX(sz, self->xnum + self->xnum);
EXPAND_IF_FULL(sz);
if (ph->start != ph->end && !l.continued) {
ph->buffer[ph->end++] = '\n';
}
while(sz < ph->maxsz - 2) {
size_t num = line_as_ansi(&l, ph->buffer + ph->end, ph->bufsize - ph->end - 2, &truncated, &prev_cell);
if (!truncated) {
ph->end += num;
ph->buffer[ph->end++] = '\r';
if (ph->bufend) {
ph->start = ph->end + 1 < ph->bufend ? ph->end + 1 : 0;
}
break;
}
// check if sz is too large too fit in buffer
if (ph->bufsize > ph->maxsz && !ph->end) break;
sz *= 2;
EXPAND_IF_FULL(sz);
}
#undef EXPAND_IF_FULL
line_as_ansi(&l, as_ansi_buf, &prev_cell);
static const Py_UCS4 nl[1] = {'\n'}, cr[1] = {'\r'}, sgr_rest[3] = {0x1b, '[', 'm'};
if (ph->start != ph->end && !l.continued) pagerhist_write(ph, nl, arraysz(nl));
pagerhist_write(ph, sgr_rest, arraysz(sgr_rest));
if (pagerhist_write(ph, as_ansi_buf->buf, as_ansi_buf->len)) pagerhist_write(ph, cr, arraysz(cr));
}
static inline index_type
historybuf_push(HistoryBuf *self) {
historybuf_push(HistoryBuf *self, ANSIBuf *as_ansi_buf) {
index_type idx = (self->start_of_data + self->count) % self->ynum;
init_line(self, idx, self->line);
if (self->count == self->ynum) {
pagerhist_push(self);
pagerhist_push(self, as_ansi_buf);
self->start_of_data = (self->start_of_data + 1) % self->ynum;
} else self->count++;
return idx;
}
void
historybuf_add_line(HistoryBuf *self, const Line *line) {
index_type idx = historybuf_push(self);
historybuf_add_line(HistoryBuf *self, const Line *line, ANSIBuf *as_ansi_buf) {
index_type idx = historybuf_push(self, as_ansi_buf);
copy_line(line, self->line);
*attrptr(self, idx) = (line->continued & CONTINUED_MASK) | (line->has_dirty_text ? TEXT_DIRTY_MASK : 0);
}
@ -266,31 +259,38 @@ push(HistoryBuf *self, PyObject *args) {
#define push_doc "Push a line into this buffer, removing the oldest line, if necessary"
Line *line;
if (!PyArg_ParseTuple(args, "O!", &Line_Type, &line)) return NULL;
historybuf_add_line(self, line);
ANSIBuf as_ansi_buf = {0};
historybuf_add_line(self, line, &as_ansi_buf);
free(as_ansi_buf.buf);
Py_RETURN_NONE;
}
static PyObject*
as_ansi(HistoryBuf *self, PyObject *callback) {
#define as_ansi_doc "as_ansi(callback) -> The contents of this buffer as ANSI escaped text. callback is called with each successive line."
static Py_UCS4 t[5120];
Line l = {.xnum=self->xnum};
bool truncated;
const GPUCell *prev_cell = NULL;
ANSIBuf output = {0};
for(unsigned int i = 0; i < self->count; i++) {
init_line(self, i, &l);
if (i < self->count - 1) {
l.continued = *attrptr(self, index_of(self, i + 1)) & CONTINUED_MASK;
} else l.continued = false;
index_type num = line_as_ansi(&l, t, 5120, &truncated, &prev_cell);
if (!(l.continued) && num < 5119) t[num++] = 10; // 10 = \n
PyObject *ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, t, num);
if (ans == NULL) return PyErr_NoMemory();
line_as_ansi(&l, &output, &prev_cell);
if (!l.continued) {
ensure_space_for(&output, buf, Py_UCS4, output.len + 1, capacity, 2048, false);
output.buf[output.len++] = 10; // 10 = \n
}
PyObject *ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, output.buf, output.len);
if (ans == NULL) { PyErr_NoMemory(); goto end; }
PyObject *ret = PyObject_CallFunctionObjArgs(callback, ans, NULL);
Py_CLEAR(ans);
if (ret == NULL) return NULL;
if (ret == NULL) goto end;
Py_CLEAR(ret);
}
end:
free(output.buf);
if (PyErr_Occurred()) return NULL;
Py_RETURN_NONE;
}
@ -400,7 +400,10 @@ static PyObject*
as_text(HistoryBuf *self, PyObject *args) {
GetLineWrapper glw = {.self=self};
glw.line.xnum = self->xnum;
return as_text_generic(args, &glw, get_line_wrapper, self->count, self->xnum);
ANSIBuf output = {0};
PyObject *ans = as_text_generic(args, &glw, get_line_wrapper, self->count, self->xnum, &output);
free(output.buf);
return ans;
}
@ -455,7 +458,7 @@ PyTypeObject HistoryBuf_Type = {
INIT_TYPE(HistoryBuf)
HistoryBuf *alloc_historybuf(unsigned int lines, unsigned int columns, unsigned int pagerhist_sz) {
return (HistoryBuf*)new(&HistoryBuf_Type, Py_BuildValue("III", lines, columns, pagerhist_sz), NULL);
return create_historybuf(&HistoryBuf_Type, columns, lines, pagerhist_sz);
}
// }}}
@ -467,13 +470,13 @@ HistoryBuf *alloc_historybuf(unsigned int lines, unsigned int columns, unsigned
#define is_src_line_continued(src_y) (map_src_index(src_y) < src->ynum - 1 ? (*attrptr(src, map_src_index(src_y + 1)) & CONTINUED_MASK) : false)
#define next_dest_line(cont) *attrptr(dest, historybuf_push(dest)) = cont & CONTINUED_MASK; dest->line->continued = cont;
#define next_dest_line(cont) *attrptr(dest, historybuf_push(dest, as_ansi_buf)) = cont & CONTINUED_MASK; dest->line->continued = cont;
#define first_dest_line next_dest_line(false);
#include "rewrap.h"
void historybuf_rewrap(HistoryBuf *self, HistoryBuf *other) {
void historybuf_rewrap(HistoryBuf *self, HistoryBuf *other, ANSIBuf *as_ansi_buf) {
while(other->num_segments < self->num_segments) add_segment(other);
if (other->xnum == self->xnum && other->ynum == self->ynum) {
// Fast path
@ -490,7 +493,7 @@ void historybuf_rewrap(HistoryBuf *self, HistoryBuf *other) {
other->count = 0; other->start_of_data = 0;
index_type x = 0, y = 0;
if (self->count > 0) {
rewrap_inner(self, other, self->count, NULL, &x, &y);
rewrap_inner(self, other, self->count, NULL, &x, &y, as_ansi_buf);
for (index_type i = 0; i < other->count; i++) *attrptr(other, (other->start_of_data + i) % other->ynum) |= TEXT_DIRTY_MASK;
}
}
@ -499,6 +502,8 @@ static PyObject*
rewrap(HistoryBuf *self, PyObject *args) {
HistoryBuf *other;
if (!PyArg_ParseTuple(args, "O!", &HistoryBuf_Type, &other)) return NULL;
historybuf_rewrap(self, other);
ANSIBuf as_ansi_buf = {0};
historybuf_rewrap(self, other, &as_ansi_buf);
free(as_ansi_buf.buf);
Py_RETURN_NONE;
}

View File

@ -399,30 +399,36 @@ delete_lines(LineBuf *self, PyObject *args) {
static PyObject*
as_ansi(LineBuf *self, PyObject *callback) {
#define as_ansi_doc "as_ansi(callback) -> The contents of this buffer as ANSI escaped text. callback is called with each successive line."
static Py_UCS4 t[5120];
Line l = {.xnum=self->xnum};
// remove trailing empty lines
index_type ylimit = self->ynum - 1;
bool truncated;
const GPUCell *prev_cell = NULL;
ANSIBuf output = {0};
do {
init_line(self, (&l), self->line_map[ylimit]);
if (line_as_ansi(&l, t, 5120, &truncated, &prev_cell) != 0) break;
line_as_ansi(&l, &output, &prev_cell);
if (output.len) break;
ylimit--;
} while(ylimit > 0);
for(index_type i = 0; i <= ylimit; i++) {
l.continued = ((i < self->ynum - 1) ? self->line_attrs[i+1] : self->line_attrs[i]) & CONTINUED_MASK;
init_line(self, (&l), self->line_map[i]);
index_type num = line_as_ansi(&l, t, 5120, &truncated, &prev_cell);
if (!(l.continued) && num < 5119) t[num++] = 10; // 10 = \n
PyObject *ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, t, num);
if (ans == NULL) return PyErr_NoMemory();
line_as_ansi(&l, &output, &prev_cell);
if (!l.continued) {
ensure_space_for(&output, buf, Py_UCS4, output.len + 1, capacity, 2048, false);
output.buf[output.len++] = 10; // 10 = \n
}
PyObject *ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, output.buf, output.len);
if (ans == NULL) { PyErr_NoMemory(); goto end; }
PyObject *ret = PyObject_CallFunctionObjArgs(callback, ans, NULL);
Py_CLEAR(ans);
if (ret == NULL) return NULL;
if (ret == NULL) goto end;
Py_CLEAR(ret);
}
end:
free(output.buf);
if (PyErr_Occurred()) return NULL;
Py_RETURN_NONE;
}
@ -435,7 +441,10 @@ get_line(void *x, int y) {
static PyObject*
as_text(LineBuf *self, PyObject *args) {
return as_text_generic(args, self, get_line, self->ynum, self->xnum);
ANSIBuf output = {0};
PyObject* ans = as_text_generic(args, self, get_line, self->ynum, self->xnum, &output);
free(output.buf);
return ans;
}
@ -528,7 +537,7 @@ copy_old(LineBuf *self, PyObject *y) {
#include "rewrap.h"
void
linebuf_rewrap(LineBuf *self, LineBuf *other, index_type *num_content_lines_before, index_type *num_content_lines_after, HistoryBuf *historybuf, index_type *track_x, index_type *track_y) {
linebuf_rewrap(LineBuf *self, LineBuf *other, index_type *num_content_lines_before, index_type *num_content_lines_after, HistoryBuf *historybuf, index_type *track_x, index_type *track_y, ANSIBuf *as_ansi_buf) {
index_type first, i;
bool is_empty = true;
@ -558,7 +567,7 @@ linebuf_rewrap(LineBuf *self, LineBuf *other, index_type *num_content_lines_befo
return;
}
rewrap_inner(self, other, first + 1, historybuf, track_x, track_y);
rewrap_inner(self, other, first + 1, historybuf, track_x, track_y, as_ansi_buf);
*num_content_lines_after = other->line->ynum + 1;
for (i = 0; i < *num_content_lines_after; i++) other->line_attrs[i] |= TEXT_DIRTY_MASK;
*num_content_lines_before = first + 1;
@ -572,7 +581,9 @@ rewrap(LineBuf *self, PyObject *args) {
if (!PyArg_ParseTuple(args, "O!O!", &LineBuf_Type, &other, &HistoryBuf_Type, &historybuf)) return NULL;
index_type x = 0, y = 0;
linebuf_rewrap(self, other, &nclb, &ncla, historybuf, &x, &y);
ANSIBuf as_ansi_buf = {0};
linebuf_rewrap(self, other, &nclb, &ncla, historybuf, &x, &y, &as_ansi_buf);
free(as_ansi_buf.buf);
return Py_BuildValue("II", nclb, ncla);
}

View File

@ -268,23 +268,23 @@ sprite_at(Line* self, PyObject *x) {
return Py_BuildValue("HHH", c->sprite_x, c->sprite_y, c->sprite_z);
}
static inline bool
write_sgr(const char *val, Py_UCS4 *buf, index_type buflen, index_type *i) {
static char s[128];
unsigned int num = snprintf(s, sizeof(s), "\x1b[%sm", val);
if (buflen - (*i) < num + 3) return false;
for(unsigned int si=0; si < num; si++) buf[(*i)++] = s[si];
return true;
static inline void
write_sgr(const char *val, ANSIBuf *output) {
#define W(c) output->buf[output->len++] = c
W(0x1b); W('[');
for (size_t i = 0; val[i] != 0 && i < 122; i++) W(val[i]);
W('m');
#undef W
}
index_type
line_as_ansi(Line *self, Py_UCS4 *buf, index_type buflen, bool *truncated, const GPUCell** prev_cell) {
#define WRITE_SGR(val) { if (!write_sgr(val, buf, buflen, &i)) { *truncated = true; return i; } }
#define WRITE_CH(val) if (i > buflen - 1) { *truncated = true; return i; } buf[i++] = val;
index_type limit = xlimit_for_line(self), i=0;
*truncated = false;
if (limit == 0) return 0;
void
line_as_ansi(Line *self, ANSIBuf *output, const GPUCell** prev_cell) {
#define ENSURE_SPACE(extra) ensure_space_for(output, buf, Py_UCS4, output->len + extra, capacity, 2048, false);
#define WRITE_SGR(val) { ENSURE_SPACE(128); write_sgr(val, output); }
#define WRITE_CH(val) { ENSURE_SPACE(1); output->buf[output->len++] = val; }
output->len = 0;
index_type limit = xlimit_for_line(self);
if (limit == 0) return;
char_type previous_width = 0;
static const GPUCell blank_cell = { 0 };
@ -320,21 +320,21 @@ line_as_ansi(Line *self, Py_UCS4 *buf, index_type buflen, bool *truncated, const
}
previous_width = cell->attrs & WIDTH_MASK;
}
return i;
#undef CMP_ATTRS
#undef CMP
#undef WRITE_SGR
#undef WRITE_CH
#undef ENSURE_SPACE
}
static PyObject*
as_ansi(Line* self, PyObject *a UNUSED) {
#define as_ansi_doc "Return the line's contents with ANSI (SGR) escape codes for formatting"
static Py_UCS4 t[5120] = {0};
bool truncated;
const GPUCell *prev_cell = NULL;
index_type num = line_as_ansi(self, t, 5120, &truncated, &prev_cell);
PyObject *ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, t, num);
ANSIBuf output = {0};
line_as_ansi(self, &output, &prev_cell);
PyObject *ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, output.buf, output.len);
free(output.buf);
return ans;
}
@ -762,7 +762,7 @@ mark_text_in_line(PyObject *marker, Line *line) {
}
PyObject*
as_text_generic(PyObject *args, void *container, get_line_func get_line, index_type lines, index_type columns) {
as_text_generic(PyObject *args, void *container, get_line_func get_line, index_type lines, index_type columns, ANSIBuf *ansibuf) {
PyObject *callback;
int as_ansi = 0, insert_wrap_markers = 0;
if (!PyArg_ParseTuple(args, "O|pp", &callback, &as_ansi, &insert_wrap_markers)) return NULL;
@ -785,16 +785,15 @@ as_text_generic(PyObject *args, void *container, get_line_func get_line, index_t
Py_CLEAR(ret);
}
if (as_ansi) {
bool truncated;
// less has a bug where it resets colors when it sees a \r, so work
// around it by resetting SGR at the start of every line. This is
// pretty sad performance wise, but I guess it will remain till I
// get around to writing a nice pager kitten.
// see https://github.com/kovidgoyal/kitty/issues/2381
prev_cell = NULL;
index_type num = line_as_ansi(line, buf, columns * 100 - 2, &truncated, &prev_cell);
t = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, num);
if (t && num > 0) {
line_as_ansi(line, ansibuf, &prev_cell);
t = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, ansibuf->buf, ansibuf->len);
if (t && ansibuf->len > 0) {
ret = PyObject_CallFunctionObjArgs(callback, sgr_reset, NULL);
if (ret == NULL) goto end;
Py_CLEAR(ret);

View File

@ -77,7 +77,7 @@ void line_add_combining_char(Line *, uint32_t , unsigned int );
index_type line_url_start_at(Line *self, index_type x);
index_type line_url_end_at(Line *self, index_type x, bool, char_type, bool);
bool line_startswith_url_chars(Line*);
index_type line_as_ansi(Line *self, Py_UCS4 *buf, index_type buflen, bool*, const GPUCell**) __attribute__((nonnull));
void line_as_ansi(Line *self, ANSIBuf *output, const GPUCell**) __attribute__((nonnull));
unsigned int line_length(Line *self);
size_t cell_as_unicode(CPUCell *cell, bool include_cc, Py_UCS4 *buf, char_type);
size_t cell_as_unicode_for_fallback(CPUCell *cell, Py_UCS4 *buf);
@ -94,14 +94,14 @@ void linebuf_clear_line(LineBuf *self, index_type y);
void linebuf_insert_lines(LineBuf *self, unsigned int num, unsigned int y, unsigned int bottom);
void linebuf_delete_lines(LineBuf *self, index_type num, index_type y, index_type bottom);
void linebuf_set_attribute(LineBuf *, unsigned int , unsigned int );
void linebuf_rewrap(LineBuf *self, LineBuf *other, index_type *, index_type *, HistoryBuf *, index_type *, index_type *);
void linebuf_rewrap(LineBuf *self, LineBuf *other, index_type *, index_type *, HistoryBuf *, index_type *, index_type *, ANSIBuf*);
void linebuf_mark_line_dirty(LineBuf *self, index_type y);
void linebuf_mark_line_clean(LineBuf *self, index_type y);
void linebuf_mark_line_as_not_continued(LineBuf *self, index_type y);
unsigned int linebuf_char_width_at(LineBuf *self, index_type x, index_type y);
void linebuf_refresh_sprite_positions(LineBuf *self);
void historybuf_add_line(HistoryBuf *self, const Line *line);
void historybuf_rewrap(HistoryBuf *self, HistoryBuf *other);
void historybuf_add_line(HistoryBuf *self, const Line *line, ANSIBuf*);
void historybuf_rewrap(HistoryBuf *self, HistoryBuf *other, ANSIBuf*);
void historybuf_init_line(HistoryBuf *self, index_type num, Line *l);
CPUCell* historybuf_cpu_cells(HistoryBuf *self, index_type num);
void historybuf_mark_line_clean(HistoryBuf *self, index_type y);
@ -110,4 +110,4 @@ void historybuf_refresh_sprite_positions(HistoryBuf *self);
void historybuf_clear(HistoryBuf *self);
void mark_text_in_line(PyObject *marker, Line *line);
bool line_has_mark(Line *, attrs_type mark);
PyObject* as_text_generic(PyObject *args, void *container, get_line_func get_line, index_type lines, index_type columns);
PyObject* as_text_generic(PyObject *args, void *container, get_line_func get_line, index_type lines, index_type columns, ANSIBuf *ansibuf);

View File

@ -30,7 +30,7 @@
if (historybuf != NULL) { \
init_dest_line(dest->ynum - 1); \
dest->line->has_dirty_text = true; \
historybuf_add_line(historybuf, dest->line); \
historybuf_add_line(historybuf, dest->line, as_ansi_buf); \
}\
linebuf_clear_line(dest, dest->ynum - 1); \
} else dest_y++; \
@ -50,7 +50,7 @@ copy_range(Line *src, index_type src_at, Line* dest, index_type dest_at, index_t
static void
rewrap_inner(BufType *src, BufType *dest, const index_type src_limit, HistoryBuf UNUSED *historybuf, index_type *track_x, index_type *track_y) {
rewrap_inner(BufType *src, BufType *dest, const index_type src_limit, HistoryBuf UNUSED *historybuf, index_type *track_x, index_type *track_y, ANSIBuf *as_ansi_buf) {
bool src_line_is_continued = false;
index_type src_y = 0, src_x = 0, dest_x = 0, dest_y = 0, num = 0, src_x_limit = 0;

View File

@ -187,19 +187,19 @@ screen_dirty_sprite_positions(Screen *self) {
}
static inline HistoryBuf*
realloc_hb(HistoryBuf *old, unsigned int lines, unsigned int columns) {
realloc_hb(HistoryBuf *old, unsigned int lines, unsigned int columns, ANSIBuf *as_ansi_buf) {
HistoryBuf *ans = alloc_historybuf(lines, columns, 0);
if (ans == NULL) { PyErr_NoMemory(); return NULL; }
ans->pagerhist = old->pagerhist; old->pagerhist = NULL;
historybuf_rewrap(old, ans);
historybuf_rewrap(old, ans, as_ansi_buf);
return ans;
}
static inline LineBuf*
realloc_lb(LineBuf *old, unsigned int lines, unsigned int columns, index_type *nclb, index_type *ncla, HistoryBuf *hb, index_type *x, index_type *y) {
realloc_lb(LineBuf *old, unsigned int lines, unsigned int columns, index_type *nclb, index_type *ncla, HistoryBuf *hb, index_type *x, index_type *y, ANSIBuf *as_ansi_buf) {
LineBuf *ans = alloc_linebuf(lines, columns);
if (ans == NULL) { PyErr_NoMemory(); return NULL; }
linebuf_rewrap(old, ans, nclb, ncla, hb, x, y);
linebuf_rewrap(old, ans, nclb, ncla, hb, x, y, as_ansi_buf);
return ans;
}
@ -221,11 +221,11 @@ screen_resize(Screen *self, unsigned int lines, unsigned int columns) {
if (!init_overlay_line(self, columns)) return false;
// Resize main linebuf
HistoryBuf *nh = realloc_hb(self->historybuf, self->historybuf->ynum, columns);
HistoryBuf *nh = realloc_hb(self->historybuf, self->historybuf->ynum, columns, &self->as_ansi_buf);
if (nh == NULL) return false;
Py_CLEAR(self->historybuf); self->historybuf = nh;
index_type x = self->cursor->x, y = self->cursor->y;
LineBuf *n = realloc_lb(self->main_linebuf, lines, columns, &num_content_lines_before, &num_content_lines_after, self->historybuf, &x, &y);
LineBuf *n = realloc_lb(self->main_linebuf, lines, columns, &num_content_lines_before, &num_content_lines_after, self->historybuf, &x, &y, &self->as_ansi_buf);
if (n == NULL) return false;
Py_CLEAR(self->main_linebuf); self->main_linebuf = n;
if (is_main) setup_cursor();
@ -233,7 +233,7 @@ screen_resize(Screen *self, unsigned int lines, unsigned int columns) {
// Resize alt linebuf
x = self->cursor->x, y = self->cursor->y;
n = realloc_lb(self->alt_linebuf, lines, columns, &num_content_lines_before, &num_content_lines_after, NULL, &x, &y);
n = realloc_lb(self->alt_linebuf, lines, columns, &num_content_lines_before, &num_content_lines_after, NULL, &x, &y, &self->as_ansi_buf);
if (n == NULL) return false;
Py_CLEAR(self->alt_linebuf); self->alt_linebuf = n;
if (!is_main) setup_cursor();
@ -302,6 +302,7 @@ dealloc(Screen* self) {
free(self->selections.items);
free(self->url_ranges.items);
free_hyperlink_pool(self->hyperlink_pool);
free(self->as_ansi_buf.buf);
Py_TYPE(self)->tp_free((PyObject*)self);
} // }}}
@ -998,7 +999,7 @@ index_selection(const Screen *self, Selections *selections, bool up) {
if (self->linebuf == self->main_linebuf && bottom == self->lines - 1) { \
/* Only add to history when no page margins have been set */ \
linebuf_init_line(self->linebuf, bottom); \
historybuf_add_line(self->historybuf, self->linebuf->line); \
historybuf_add_line(self->historybuf, self->linebuf->line, &self->as_ansi_buf); \
self->history_line_added_count++; \
} \
linebuf_clear_line(self->linebuf, bottom); \
@ -2029,17 +2030,17 @@ static Line* get_range_line(void *x, int y) { return range_line_(x, y); }
static PyObject*
as_text(Screen *self, PyObject *args) {
return as_text_generic(args, self, get_visual_line, self->lines, self->columns);
return as_text_generic(args, self, get_visual_line, self->lines, self->columns, &self->as_ansi_buf);
}
static PyObject*
as_text_non_visual(Screen *self, PyObject *args) {
return as_text_generic(args, self, get_range_line, self->lines, self->columns);
return as_text_generic(args, self, get_range_line, self->lines, self->columns, &self->as_ansi_buf);
}
static inline PyObject*
as_text_generic_wrapper(Screen *self, PyObject *args, get_line_func get_line) {
return as_text_generic(args, self, get_line, self->lines, self->columns);
return as_text_generic(args, self, get_line, self->lines, self->columns, &self->as_ansi_buf);
}
static PyObject*

View File

@ -132,6 +132,7 @@ typedef struct {
bool has_activity_since_last_focus;
hyperlink_id_type active_hyperlink_id;
HYPERLINK_POOL_HANDLE hyperlink_pool;
ANSIBuf as_ansi_buf;
} Screen;

View File

@ -142,6 +142,36 @@ def calculate_gl_geometry(window_geometry: WindowGeometry, viewport_width: int,
return ScreenGeometry(xstart, ystart, window_geometry.xnum, window_geometry.ynum, dx, dy)
def as_text(
screen: Screen,
as_ansi: bool = False,
add_history: bool = False,
add_wrap_markers: bool = False,
alternate_screen: bool = False
) -> str:
lines: List[str] = []
add_history = add_history and not (screen.is_using_alternate_linebuf() ^ alternate_screen)
if alternate_screen:
f = screen.as_text_alternate
else:
f = screen.as_text_non_visual if add_history else screen.as_text
f(lines.append, as_ansi, add_wrap_markers)
if add_history:
h: List[str] = []
screen.historybuf.pagerhist_as_text(h.append)
if h and (not as_ansi or not add_wrap_markers):
sanitizer = text_sanitizer(as_ansi, add_wrap_markers)
h = list(map(sanitizer, h))
screen.historybuf.as_text(h.append, as_ansi, add_wrap_markers)
if h:
if not screen.linebuf.is_continued(0):
h[-1] += '\n'
if as_ansi:
h[-1] += '\x1b[m'
return ''.join(chain(h, lines))
return ''.join(lines)
class LoadShaderPrograms:
use_selection_fg = True
@ -772,27 +802,7 @@ class Window:
add_wrap_markers: bool = False,
alternate_screen: bool = False
) -> str:
lines: List[str] = []
add_history = add_history and not (self.screen.is_using_alternate_linebuf() ^ alternate_screen)
if alternate_screen:
f = self.screen.as_text_alternate
else:
f = self.screen.as_text_non_visual if add_history else self.screen.as_text
f(lines.append, as_ansi, add_wrap_markers)
if add_history:
h: List[str] = []
self.screen.historybuf.pagerhist_as_text(h.append)
if h and (not as_ansi or not add_wrap_markers):
sanitizer = text_sanitizer(as_ansi, add_wrap_markers)
h = list(map(sanitizer, h))
self.screen.historybuf.as_text(h.append, as_ansi, add_wrap_markers)
if h:
if not self.screen.linebuf.is_continued(0):
h[-1] += '\n'
if as_ansi:
h[-1] += '\x1b[m'
return ''.join(chain(h, lines))
return ''.join(lines)
return as_text(self.screen, as_ansi, add_history, add_wrap_markers, alternate_screen)
@property
def cwd_of_child(self) -> Optional[str]:

View File

@ -252,8 +252,7 @@ class TestScreen(BaseTest):
self.ae(str(s.line(2)), '4'*5)
s.resize(5, 1)
self.ae(str(s.line(0)), '4')
hb = s.historybuf
self.ae(str(hb), '3\n3\n3\n3\n3\n2')
self.ae(str(s.historybuf), '3\n3\n3\n3\n3\n2')
s = self.create_screen(scrollback=20)
s.draw(''.join(str(i) * s.columns for i in range(s.lines*2)))
self.ae(str(s.linebuf), '55555\n66666\n77777\n88888\n99999')
@ -453,18 +452,20 @@ class TestScreen(BaseTest):
self.ae(s.cursor.x, 2)
def test_serialize(self):
from kitty.window import as_text
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(s), 'ababababab\nc\n\n')
self.ae(as_text(s, True), '\x1b[mababa\x1b[mbabab\n\x1b[mc\n\n')
self.ae(as_text(), 'ababababab\nc\n\n')
self.ae(as_text(True), '\x1b[mababa\x1b[mbabab\n\x1b[mc\n\n')
s = self.create_screen(cols=2, lines=2, scrollback=2)
for i in range(1, 7):
s.select_graphic_rendition(30 + i)
s.draw(f'{i}' * s.columns)
self.ae(as_text(s, True, True), '\x1b[m\x1b[31m11\x1b[m\x1b[32m22\x1b[m\x1b[33m33\x1b[m\x1b[34m44\x1b[m\x1b[m\x1b[35m55\x1b[m\x1b[36m66')
def test_user_marking(self):