mirror of
https://github.com/kovidgoyal/kitty.git
synced 2024-10-26 15:13:22 +03:00
Switch to tracking linewrap on the last cell in a line
This allows us to have newline not affect the wrap status of a line. Now a lines wrapping status is changed only when the last cell in the line is changed. This actually matches the behavior of many other terminal emulators so is probably a good thing from a ecosystem compatibility perspective. The fish shell expects this weird behavior of newline not changing wrapping status, for unknown reasons, which is the actual motivation for doing all this work. Fixes #5766
This commit is contained in:
parent
4556f5b8f1
commit
68cf9f7514
@ -158,6 +158,7 @@ typedef union CellAttrs {
|
||||
uint16_t strike : 1;
|
||||
uint16_t dim : 1;
|
||||
uint16_t mark : 2;
|
||||
uint16_t next_char_was_wrapped : 1;
|
||||
};
|
||||
uint16_t val;
|
||||
} CellAttrs;
|
||||
@ -165,7 +166,7 @@ typedef union CellAttrs {
|
||||
#define WIDTH_MASK (3u)
|
||||
#define DECORATION_MASK (7u)
|
||||
#define NUM_UNDERLINE_STYLES (5u)
|
||||
#define SGR_MASK (~(((CellAttrs){.width=WIDTH_MASK, .mark=MARK_MASK}).val))
|
||||
#define SGR_MASK (~(((CellAttrs){.width=WIDTH_MASK, .mark=MARK_MASK, .next_char_was_wrapped=1}).val))
|
||||
|
||||
typedef struct {
|
||||
color_type fg, bg, decoration_fg;
|
||||
@ -184,7 +185,7 @@ static_assert(sizeof(CPUCell) == 12, "Fix the ordering of CPUCell");
|
||||
typedef enum { UNKNOWN_PROMPT_KIND = 0, PROMPT_START = 1, SECONDARY_PROMPT = 2, OUTPUT_START = 3 } PromptKind;
|
||||
typedef union LineAttrs {
|
||||
struct {
|
||||
uint8_t continued : 1;
|
||||
uint8_t is_continued : 1;
|
||||
uint8_t has_dirty_text : 1;
|
||||
PromptKind prompt_kind : 2;
|
||||
};
|
||||
|
@ -164,6 +164,16 @@ init_line(HistoryBuf *self, index_type num, Line *l) {
|
||||
l->cpu_cells = cpu_lineptr(self, num);
|
||||
l->gpu_cells = gpu_lineptr(self, num);
|
||||
l->attrs = *attrptr(self, num);
|
||||
if (num > 0) {
|
||||
l->attrs.is_continued = gpu_lineptr(self, num - 1)[self->xnum-1].attrs.next_char_was_wrapped;
|
||||
} else {
|
||||
l->attrs.is_continued = false;
|
||||
size_t sz;
|
||||
if (self->pagerhist && self->pagerhist->ringbuf && (sz = ringbuf_bytes_used(self->pagerhist->ringbuf)) > 0) {
|
||||
size_t pos = ringbuf_findchr(self->pagerhist->ringbuf, '\n', sz - 1);
|
||||
if (pos >= sz) l->attrs.is_continued = true; // ringbuf does not end with a newline
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@ -171,6 +181,11 @@ historybuf_init_line(HistoryBuf *self, index_type lnum, Line *l) {
|
||||
init_line(self, index_of(self, lnum), l);
|
||||
}
|
||||
|
||||
bool
|
||||
history_buf_endswith_wrap(HistoryBuf *self) {
|
||||
return gpu_lineptr(self, index_of(self, 0))[self->xnum-1].attrs.next_char_was_wrapped;
|
||||
}
|
||||
|
||||
CPUCell*
|
||||
historybuf_cpu_cells(HistoryBuf *self, index_type lnum) {
|
||||
return cpu_lineptr(self, index_of(self, lnum));
|
||||
@ -243,9 +258,13 @@ pagerhist_push(HistoryBuf *self, ANSIBuf *as_ansi_buf) {
|
||||
Line l = {.xnum=self->xnum};
|
||||
init_line(self, self->start_of_data, &l);
|
||||
line_as_ansi(&l, as_ansi_buf, &prev_cell, 0, l.xnum, 0);
|
||||
if (ringbuf_bytes_used(ph->ringbuf) && !l.attrs.continued) pagerhist_write_bytes(ph, (const uint8_t*)"\n", 1);
|
||||
pagerhist_write_bytes(ph, (const uint8_t*)"\x1b[m", 3);
|
||||
if (pagerhist_write_ucs4(ph, as_ansi_buf->buf, as_ansi_buf->len)) pagerhist_write_bytes(ph, (const uint8_t*)"\r", 1);
|
||||
if (pagerhist_write_ucs4(ph, as_ansi_buf->buf, as_ansi_buf->len)) {
|
||||
char line_end[2]; size_t num = 0;
|
||||
line_end[num++] = '\r';
|
||||
if (!l.gpu_cells[l.xnum - 1].attrs.next_char_was_wrapped) line_end[num++] = '\n';
|
||||
pagerhist_write_bytes(ph, (const uint8_t*)line_end, num);
|
||||
}
|
||||
}
|
||||
|
||||
static index_type
|
||||
@ -275,6 +294,13 @@ historybuf_pop_line(HistoryBuf *self, Line *line) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
history_buf_set_last_char_as_continuation(HistoryBuf *self, index_type y, bool wrapped) {
|
||||
if (self->count > 0) {
|
||||
gpu_lineptr(self, index_of(self, y))[self->xnum-1].attrs.next_char_was_wrapped = wrapped;
|
||||
}
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
line(HistoryBuf *self, PyObject *val) {
|
||||
#define line_doc "Return the line with line number val. This buffer grows upwards, i.e. 0 is the most recently added line"
|
||||
@ -321,13 +347,10 @@ as_ansi(HistoryBuf *self, PyObject *callback) {
|
||||
ANSIBuf output = {0};
|
||||
for(unsigned int i = 0; i < self->count; i++) {
|
||||
init_line(self, i, &l);
|
||||
if (i < self->count - 1) {
|
||||
l.attrs.continued = attrptr(self, index_of(self, i + 1))->continued;
|
||||
} else l.attrs.continued = false;
|
||||
line_as_ansi(&l, &output, &prev_cell, 0, l.xnum, 0);
|
||||
if (!l.attrs.continued) {
|
||||
if (!l.gpu_cells[l.xnum - 1].attrs.next_char_was_wrapped) {
|
||||
ensure_space_for(&output, buf, Py_UCS4, output.len + 1, capacity, 2048, false);
|
||||
output.buf[output.len++] = 10; // 10 = \n
|
||||
output.buf[output.len++] = '\n';
|
||||
}
|
||||
PyObject *ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, output.buf, output.len);
|
||||
if (ans == NULL) { PyErr_NoMemory(); goto end; }
|
||||
@ -438,14 +461,11 @@ pagerhist_as_bytes(HistoryBuf *self, PyObject *args) {
|
||||
pagerhist_ensure_start_is_valid_utf8(ph);
|
||||
if (ph->rewrap_needed) pagerhist_rewrap_to(self, self->xnum);
|
||||
|
||||
Line l = {.xnum=self->xnum}; get_line(self, 0, &l);
|
||||
size_t sz = ringbuf_bytes_used(ph->ringbuf);
|
||||
if (!l.attrs.continued) sz += 1;
|
||||
PyObject *ans = PyBytes_FromStringAndSize(NULL, sz);
|
||||
if (!ans) return NULL;
|
||||
uint8_t *buf = (uint8_t*)PyBytes_AS_STRING(ans);
|
||||
ringbuf_memcpy_from(buf, ph->ringbuf, sz);
|
||||
if (!l.attrs.continued) buf[sz-1] = '\n';
|
||||
if (upto_output_start) {
|
||||
const uint8_t *p = reverse_find(buf, sz, (const uint8_t*)"\x1b]133;C\x1b\\");
|
||||
if (p) {
|
||||
@ -484,7 +504,7 @@ PyObject*
|
||||
as_text_history_buf(HistoryBuf *self, PyObject *args, ANSIBuf *output) {
|
||||
GetLineWrapper glw = {.self=self};
|
||||
glw.line.xnum = self->xnum;
|
||||
PyObject *ans = as_text_generic(args, &glw, get_line_wrapper, self->count, output);
|
||||
PyObject *ans = as_text_generic(args, &glw, get_line_wrapper, self->count, output, true);
|
||||
return ans;
|
||||
}
|
||||
|
||||
@ -560,9 +580,7 @@ HistoryBuf *alloc_historybuf(unsigned int lines, unsigned int columns, unsigned
|
||||
|
||||
#define init_src_line(src_y) init_line(src, map_src_index(src_y), src->line);
|
||||
|
||||
#define is_src_line_continued(src_y) (map_src_index(src_y) < src->ynum - 1 ? (attrptr(src, map_src_index(src_y + 1))->continued) : false)
|
||||
|
||||
#define next_dest_line(cont) { LineAttrs *lap = attrptr(dest, historybuf_push(dest, as_ansi_buf)); *lap = src->line->attrs; if (cont) lap->continued = true; dest->line->attrs.continued = cont; }
|
||||
#define next_dest_line(cont) { history_buf_set_last_char_as_continuation(dest, 0, cont); LineAttrs *lap = attrptr(dest, historybuf_push(dest, as_ansi_buf)); *lap = src->line->attrs; }
|
||||
|
||||
#define first_dest_line next_dest_line(false);
|
||||
|
||||
|
@ -130,6 +130,7 @@ linebuf_init_line(LineBuf *self, index_type idx) {
|
||||
self->line->ynum = idx;
|
||||
self->line->xnum = self->xnum;
|
||||
self->line->attrs = self->line_attrs[idx];
|
||||
self->line->attrs.is_continued = idx > 0 ? gpu_lineptr(self, self->line_map[idx - 1])[self->xnum - 1].attrs.next_char_was_wrapped : false;
|
||||
init_line(self, self->line, self->line_map[idx]);
|
||||
}
|
||||
|
||||
@ -151,6 +152,19 @@ linebuf_char_width_at(LineBuf *self, index_type x, index_type y) {
|
||||
return gpu_lineptr(self, self->line_map[y])[x].attrs.width;
|
||||
}
|
||||
|
||||
bool
|
||||
linebuf_line_ends_with_continuation(LineBuf *self, index_type y) {
|
||||
return y < self->ynum ? gpu_lineptr(self, self->line_map[y])[self->xnum - 1].attrs.next_char_was_wrapped : false;
|
||||
}
|
||||
|
||||
void
|
||||
linebuf_set_last_char_as_continuation(LineBuf *self, index_type y, bool continued) {
|
||||
if (y < self->ynum) {
|
||||
gpu_lineptr(self, self->line_map[y])[self->xnum - 1].attrs.next_char_was_wrapped = continued;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static PyObject*
|
||||
set_attribute(LineBuf *self, PyObject *args) {
|
||||
#define set_attribute_doc "set_attribute(which, val) -> Set the attribute on all cells in the line."
|
||||
@ -172,8 +186,8 @@ set_continued(LineBuf *self, PyObject *args) {
|
||||
unsigned int y;
|
||||
int val;
|
||||
if (!PyArg_ParseTuple(args, "Ip", &y, &val)) return NULL;
|
||||
if (y >= self->ynum) { PyErr_SetString(PyExc_ValueError, "Out of bounds."); return NULL; }
|
||||
self->line_attrs[y].continued = val;
|
||||
if (y > self->ynum || y < 1) { PyErr_SetString(PyExc_ValueError, "Out of bounds."); return NULL; }
|
||||
linebuf_set_last_char_as_continuation(self, y-1, val);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
@ -316,7 +330,7 @@ is_continued(LineBuf *self, PyObject *val) {
|
||||
#define is_continued_doc "is_continued(y) -> Whether the line y is continued or not"
|
||||
unsigned long y = PyLong_AsUnsignedLong(val);
|
||||
if (y >= self->ynum) { PyErr_SetString(PyExc_ValueError, "Out of bounds."); return NULL; }
|
||||
if (self->line_attrs[y].continued) { Py_RETURN_TRUE; }
|
||||
if (y > 0 && linebuf_line_ends_with_continuation(self, y-1)) { Py_RETURN_TRUE; }
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
|
||||
@ -334,7 +348,6 @@ linebuf_insert_lines(LineBuf *self, unsigned int num, unsigned int y, unsigned i
|
||||
self->line_map[i] = self->line_map[i - num];
|
||||
self->line_attrs[i] = self->line_attrs[i - num];
|
||||
}
|
||||
if (y + num < self->ynum) self->line_attrs[y + num].continued = false;
|
||||
for (i = 0; i < num; i++) {
|
||||
self->line_map[y + i] = self->scratch[ylimit - num + i];
|
||||
}
|
||||
@ -369,7 +382,6 @@ linebuf_delete_lines(LineBuf *self, index_type num, index_type y, index_type bot
|
||||
self->line_map[i] = self->line_map[i + num];
|
||||
self->line_attrs[i] = self->line_attrs[i + num];
|
||||
}
|
||||
self->line_attrs[y].continued = false;
|
||||
for (i = 0; i < num; i++) {
|
||||
self->line_map[ylimit - num + i] = self->scratch[y + i];
|
||||
}
|
||||
@ -414,10 +426,10 @@ as_ansi(LineBuf *self, PyObject *callback) {
|
||||
} while(ylimit > 0);
|
||||
|
||||
for(index_type i = 0; i <= ylimit; i++) {
|
||||
l.attrs.continued = self->line_attrs[(i + 1 < self->ynum) ? i+1 : i].continued;
|
||||
bool output_newline = !linebuf_line_ends_with_continuation(self, i);
|
||||
init_line(self, (&l), self->line_map[i]);
|
||||
line_as_ansi(&l, &output, &prev_cell, 0, l.xnum, 0);
|
||||
if (!l.attrs.continued) {
|
||||
if (output_newline) {
|
||||
ensure_space_for(&output, buf, Py_UCS4, output.len + 1, capacity, 2048, false);
|
||||
output.buf[output.len++] = 10; // 10 = \n
|
||||
}
|
||||
@ -444,7 +456,7 @@ get_line(void *x, int y) {
|
||||
static PyObject*
|
||||
as_text(LineBuf *self, PyObject *args) {
|
||||
ANSIBuf output = {0};
|
||||
PyObject* ans = as_text_generic(args, self, get_line, self->ynum, &output);
|
||||
PyObject* ans = as_text_generic(args, self, get_line, self->ynum, &output, false);
|
||||
free(output.buf);
|
||||
return ans;
|
||||
}
|
||||
|
26
kitty/line.c
26
kitty/line.c
@ -229,10 +229,9 @@ cell_as_utf8_for_fallback(CPUCell *cell, char *buf) {
|
||||
|
||||
|
||||
PyObject*
|
||||
unicode_in_range(const Line *self, const index_type start, const index_type limit, const bool include_cc, const char leading_char, const bool skip_zero_cells) {
|
||||
unicode_in_range(const Line *self, const index_type start, const index_type limit, const bool include_cc, const bool add_trailing_newline, const bool skip_zero_cells) {
|
||||
size_t n = 0;
|
||||
static Py_UCS4 buf[4096];
|
||||
if (leading_char) buf[n++] = leading_char;
|
||||
char_type previous_width = 0;
|
||||
for(index_type i = start; i < limit && n < arraysz(buf) - 2 - arraysz(self->cpu_cells->cc_idx); i++) {
|
||||
char_type ch = self->cpu_cells[i].ch;
|
||||
@ -252,12 +251,15 @@ unicode_in_range(const Line *self, const index_type start, const index_type limi
|
||||
}
|
||||
previous_width = self->gpu_cells[i].attrs.width;
|
||||
}
|
||||
if (add_trailing_newline && !self->gpu_cells[self->xnum-1].attrs.next_char_was_wrapped && n < arraysz(buf)) {
|
||||
buf[n++] = '\n';
|
||||
}
|
||||
return PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, n);
|
||||
}
|
||||
|
||||
PyObject *
|
||||
line_as_unicode(Line* self, bool skip_zero_cells) {
|
||||
return unicode_in_range(self, 0, xlimit_for_line(self), true, 0, skip_zero_cells);
|
||||
return unicode_in_range(self, 0, xlimit_for_line(self), true, false, skip_zero_cells);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
@ -401,11 +403,10 @@ as_ansi(Line* self, PyObject *a UNUSED) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
is_continued(Line* self, PyObject *a UNUSED) {
|
||||
#define is_continued_doc "Return the line's continued flag"
|
||||
PyObject *ans = self->attrs.continued ? Py_True : Py_False;
|
||||
Py_INCREF(ans);
|
||||
return ans;
|
||||
last_char_has_wrapped_flag(Line* self, PyObject *a UNUSED) {
|
||||
#define last_char_has_wrapped_flag_doc "Return True if the last cell of this line has the wrapped flags set"
|
||||
if (self->gpu_cells[self->xnum - 1].attrs.next_char_was_wrapped) { Py_RETURN_TRUE; }
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
@ -839,7 +840,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, ANSIBuf *ansibuf) {
|
||||
as_text_generic(PyObject *args, void *container, get_line_func get_line, index_type lines, ANSIBuf *ansibuf, bool add_trailing_newline) {
|
||||
#define APPEND(x) { PyObject* retval = PyObject_CallFunctionObjArgs(callback, x, NULL); if (!retval) return NULL; Py_DECREF(retval); }
|
||||
#define APPEND_AND_DECREF(x) { if (x == NULL) { if (PyErr_Occurred()) return NULL; Py_RETURN_NONE; } PyObject* retval = PyObject_CallFunctionObjArgs(callback, x, NULL); Py_CLEAR(x); if (!retval) return NULL; Py_DECREF(retval); }
|
||||
PyObject *callback;
|
||||
@ -852,10 +853,11 @@ as_text_generic(PyObject *args, void *container, get_line_func get_line, index_t
|
||||
if (nl == NULL || cr == NULL || sgr_reset == NULL) return NULL;
|
||||
const GPUCell *prev_cell = NULL;
|
||||
ansibuf->active_hyperlink_id = 0;
|
||||
bool need_newline = false;
|
||||
for (index_type y = 0; y < lines; y++) {
|
||||
Line *line = get_line(container, y);
|
||||
if (!line) { if (PyErr_Occurred()) return NULL; break; }
|
||||
if (!line->attrs.continued && y > 0) APPEND(nl);
|
||||
if (need_newline) APPEND(nl);
|
||||
if (as_ansi) {
|
||||
// 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
|
||||
@ -871,7 +873,9 @@ as_text_generic(PyObject *args, void *container, get_line_func get_line, index_t
|
||||
}
|
||||
APPEND_AND_DECREF(t);
|
||||
if (insert_wrap_markers) APPEND(cr);
|
||||
need_newline = !line->gpu_cells[line->xnum-1].attrs.next_char_was_wrapped;
|
||||
}
|
||||
if (need_newline && add_trailing_newline) APPEND(nl);
|
||||
if (ansibuf->active_hyperlink_id) {
|
||||
ansibuf->active_hyperlink_id = 0;
|
||||
t = PyUnicode_FromString("\x1b]8;;\x1b\\");
|
||||
@ -919,7 +923,7 @@ static PyMethodDef methods[] = {
|
||||
METHOD(set_char, METH_VARARGS)
|
||||
METHOD(set_attribute, METH_VARARGS)
|
||||
METHOD(as_ansi, METH_NOARGS)
|
||||
METHOD(is_continued, METH_NOARGS)
|
||||
METHOD(last_char_has_wrapped_flag, METH_NOARGS)
|
||||
METHOD(hyperlink_ids, METH_NOARGS)
|
||||
METHOD(width, METH_O)
|
||||
METHOD(url_start_at, METH_O)
|
||||
|
@ -97,7 +97,7 @@ 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);
|
||||
size_t cell_as_utf8(CPUCell *cell, bool include_cc, char *buf, char_type);
|
||||
size_t cell_as_utf8_for_fallback(CPUCell *cell, char *buf);
|
||||
PyObject* unicode_in_range(const Line *self, const index_type start, const index_type limit, const bool include_cc, const char leading_char, const bool skip_zero_cells);
|
||||
PyObject* unicode_in_range(const Line *self, const index_type start, const index_type limit, const bool include_cc, const bool add_trailing_newline, const bool skip_zero_cells);
|
||||
PyObject* line_as_unicode(Line *, bool);
|
||||
|
||||
void linebuf_init_line(LineBuf *, index_type);
|
||||
@ -113,11 +113,14 @@ void linebuf_mark_line_dirty(LineBuf *self, index_type y);
|
||||
void linebuf_clear_attrs_and_dirty(LineBuf *self, index_type y);
|
||||
void linebuf_mark_line_clean(LineBuf *self, index_type y);
|
||||
unsigned int linebuf_char_width_at(LineBuf *self, index_type x, index_type y);
|
||||
void linebuf_set_last_char_as_continuation(LineBuf *self, index_type y, bool continued);
|
||||
bool linebuf_line_ends_with_continuation(LineBuf *self, index_type y);
|
||||
void linebuf_refresh_sprite_positions(LineBuf *self);
|
||||
void historybuf_add_line(HistoryBuf *self, const Line *line, ANSIBuf*);
|
||||
bool historybuf_pop_line(HistoryBuf *, Line *);
|
||||
void historybuf_rewrap(HistoryBuf *self, HistoryBuf *other, ANSIBuf*);
|
||||
void historybuf_init_line(HistoryBuf *self, index_type num, Line *l);
|
||||
bool history_buf_endswith_wrap(HistoryBuf *self);
|
||||
CPUCell* historybuf_cpu_cells(HistoryBuf *self, index_type num);
|
||||
void historybuf_mark_line_clean(HistoryBuf *self, index_type y);
|
||||
void historybuf_mark_line_dirty(HistoryBuf *self, index_type y);
|
||||
@ -125,5 +128,5 @@ 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 *, uint16_t mark);
|
||||
PyObject* as_text_generic(PyObject *args, void *container, get_line_func get_line, index_type lines, ANSIBuf *ansibuf);
|
||||
PyObject* as_text_generic(PyObject *args, void *container, get_line_func get_line, index_type lines, ANSIBuf *ansibuf, bool add_trailing_newline);
|
||||
bool colors_for_cell(Line *self, ColorProfile *cp, index_type *x, color_type *fg, color_type *bg);
|
||||
|
@ -15,14 +15,15 @@
|
||||
#define init_src_line(src_y) linebuf_init_line(src, src_y);
|
||||
#endif
|
||||
|
||||
#define set_dest_line_attrs(dest_y, continued_) dest->line_attrs[dest_y] = src->line->attrs; if (continued_) dest->line_attrs[dest_y].continued = true; src->line->attrs.prompt_kind = UNKNOWN_PROMPT_KIND;
|
||||
#define set_dest_line_attrs(dest_y) dest->line_attrs[dest_y] = src->line->attrs; src->line->attrs.prompt_kind = UNKNOWN_PROMPT_KIND;
|
||||
|
||||
#ifndef first_dest_line
|
||||
#define first_dest_line linebuf_init_line(dest, 0); set_dest_line_attrs(0, false)
|
||||
#define first_dest_line linebuf_init_line(dest, 0); set_dest_line_attrs(0)
|
||||
#endif
|
||||
|
||||
#ifndef next_dest_line
|
||||
#define next_dest_line(continued) \
|
||||
linebuf_set_last_char_as_continuation(dest, dest_y, continued); \
|
||||
if (dest_y >= dest->ynum - 1) { \
|
||||
linebuf_index(dest, 0, dest->ynum - 1); \
|
||||
if (historybuf != NULL) { \
|
||||
@ -33,11 +34,11 @@
|
||||
linebuf_clear_line(dest, dest->ynum - 1, true); \
|
||||
} else dest_y++; \
|
||||
linebuf_init_line(dest, dest_y); \
|
||||
set_dest_line_attrs(dest_y, continued);
|
||||
set_dest_line_attrs(dest_y);
|
||||
#endif
|
||||
|
||||
#ifndef is_src_line_continued
|
||||
#define is_src_line_continued(src_y) (src_y + 1 < src->ynum ? (src->line_attrs[src_y + 1].continued) : false)
|
||||
#define is_src_line_continued() (src->line->gpu_cells[src->xnum-1].attrs.next_char_was_wrapped)
|
||||
#endif
|
||||
|
||||
static inline void
|
||||
@ -54,7 +55,7 @@ typedef struct TrackCursor {
|
||||
|
||||
static void
|
||||
rewrap_inner(BufType *src, BufType *dest, const index_type src_limit, HistoryBuf UNUSED *historybuf, TrackCursor *track, ANSIBuf *as_ansi_buf) {
|
||||
bool src_line_is_continued = false, is_first_line = true;
|
||||
bool is_first_line = true;
|
||||
index_type src_y = 0, src_x = 0, dest_x = 0, dest_y = 0, num = 0, src_x_limit = 0;
|
||||
TrackCursor tc_end = {.is_sentinel = true };
|
||||
if (!track) track = &tc_end;
|
||||
@ -62,11 +63,13 @@ rewrap_inner(BufType *src, BufType *dest, const index_type src_limit, HistoryBuf
|
||||
do {
|
||||
for (TrackCursor *t = track; !t->is_sentinel; t++) t->is_tracked_line = src_y == t->y;
|
||||
init_src_line(src_y);
|
||||
src_line_is_continued = is_src_line_continued(src_y);
|
||||
const bool src_line_is_continued = is_src_line_continued();
|
||||
src_x_limit = src->xnum;
|
||||
if (!src_line_is_continued) {
|
||||
// Trim trailing blanks since there is a hard line break at the end of this line
|
||||
while(src_x_limit && (src->line->cpu_cells[src_x_limit - 1].ch) == BLANK_CHAR) src_x_limit--;
|
||||
} else {
|
||||
src->line->gpu_cells[src->xnum-1].attrs.next_char_was_wrapped = false;
|
||||
}
|
||||
for (TrackCursor *t = track; !t->is_sentinel; t++) {
|
||||
if (t->is_tracked_line && t->x >= src_x_limit) t->x = MAX(1u, src_x_limit) - 1;
|
||||
|
@ -317,7 +317,6 @@ found:
|
||||
// so when resizing, simply blank all lines after the current
|
||||
// prompt and trust the shell to redraw them.
|
||||
for (; y < (int)self->main_linebuf->ynum; y++) {
|
||||
self->main_linebuf->line_attrs[y].continued = false;
|
||||
linebuf_clear_line(self->main_linebuf, y, false);
|
||||
linebuf_init_line(self->main_linebuf, y);
|
||||
if (y <= (int)self->cursor->y) {
|
||||
@ -506,9 +505,9 @@ move_widened_char(Screen *self, CPUCell* cpu_cell, GPUCell *gpu_cell, index_type
|
||||
line_clear_text(self->linebuf->line, xpos, 1, BLANK_CHAR);
|
||||
|
||||
if (self->modes.mDECAWM) { // overflow goes onto next line
|
||||
linebuf_set_last_char_as_continuation(self->linebuf, self->cursor->y, true);
|
||||
screen_carriage_return(self);
|
||||
screen_linefeed(self);
|
||||
self->linebuf->line_attrs[self->cursor->y].continued = true;
|
||||
linebuf_init_line(self->linebuf, self->cursor->y);
|
||||
dest_cpu = self->linebuf->line->cpu_cells;
|
||||
dest_gpu = self->linebuf->line->gpu_cells;
|
||||
@ -680,9 +679,9 @@ draw_codepoint(Screen *self, char_type och, bool from_input_stream) {
|
||||
if (from_input_stream) self->last_graphic_char = ch;
|
||||
if (UNLIKELY(self->columns - self->cursor->x < (unsigned int)char_width)) {
|
||||
if (self->modes.mDECAWM) {
|
||||
linebuf_set_last_char_as_continuation(self->linebuf, self->cursor->y, true);
|
||||
screen_carriage_return(self);
|
||||
screen_linefeed(self);
|
||||
self->linebuf->line_attrs[self->cursor->y].continued = true;
|
||||
} else {
|
||||
self->cursor->x = self->columns - char_width;
|
||||
}
|
||||
@ -740,7 +739,7 @@ get_overlay_text(Screen *self) {
|
||||
if (ol.ynum >= self->lines || ol.xnum >= self->columns || !ol.xnum) return NULL;
|
||||
Line *line = range_line_(self, ol.ynum);
|
||||
if (!line) return NULL;
|
||||
return unicode_in_range(line, ol.xstart, ol.xstart + ol.xnum, true, 0, true);
|
||||
return unicode_in_range(line, ol.xstart, ol.xstart + ol.xnum, true, false, true);
|
||||
#undef ol
|
||||
}
|
||||
|
||||
@ -1368,7 +1367,6 @@ screen_linefeed(Screen *self) {
|
||||
bool in_margins = cursor_within_margins(self);
|
||||
screen_index(self);
|
||||
if (self->modes.mLNM) screen_carriage_return(self);
|
||||
if (self->cursor->y < self->lines) self->linebuf->line_attrs[self->cursor->y].continued = false;
|
||||
screen_ensure_bounds(self, false, in_margins);
|
||||
}
|
||||
|
||||
@ -1674,6 +1672,7 @@ screen_erase_in_display(Screen *self, unsigned int how, bool private) {
|
||||
linebuf_init_line(self->linebuf, i);
|
||||
if (private) {
|
||||
line_clear_text(self->linebuf->line, 0, self->columns, BLANK_CHAR);
|
||||
linebuf_set_last_char_as_continuation(self->linebuf, i, false);
|
||||
} else {
|
||||
line_apply_cursor(self->linebuf->line, self->cursor, 0, self->columns, true);
|
||||
}
|
||||
@ -2312,6 +2311,15 @@ num_lines_between_selection_boundaries(const SelectionBoundary *a, const Selecti
|
||||
|
||||
typedef Line*(linefunc_t)(Screen*, int);
|
||||
|
||||
static Line*
|
||||
init_line(Screen *self, index_type y) {
|
||||
linebuf_init_line(self->linebuf, y);
|
||||
if (y == 0 && self->linebuf == self->main_linebuf) {
|
||||
if (history_buf_endswith_wrap(self->historybuf)) self->linebuf->line->attrs.is_continued = true;
|
||||
}
|
||||
return self->linebuf->line;
|
||||
}
|
||||
|
||||
static Line*
|
||||
visual_line_(Screen *self, int y_) {
|
||||
index_type y = MAX(0, y_);
|
||||
@ -2322,8 +2330,7 @@ visual_line_(Screen *self, int y_) {
|
||||
}
|
||||
y -= self->scrolled_by;
|
||||
}
|
||||
linebuf_init_line(self->linebuf, y);
|
||||
return self->linebuf->line;
|
||||
return init_line(self, y);
|
||||
}
|
||||
|
||||
static Line*
|
||||
@ -2332,8 +2339,7 @@ range_line_(Screen *self, int y) {
|
||||
historybuf_init_line(self->historybuf, -(y + 1), self->historybuf->line);
|
||||
return self->historybuf->line;
|
||||
}
|
||||
linebuf_init_line(self->linebuf, y);
|
||||
return self->linebuf->line;
|
||||
return init_line(self, y);
|
||||
}
|
||||
|
||||
static Line*
|
||||
@ -2512,7 +2518,6 @@ text_for_range(Screen *self, const Selection *sel, bool insert_newlines, bool st
|
||||
for (int i = 0, y = idata.y; y < limit; y++, i++) {
|
||||
Line *line = range_line_(self, y);
|
||||
XRange xr = xrange_for_iteration(&idata, y, line);
|
||||
char leading_char = (i > 0 && insert_newlines && !line->attrs.continued) ? '\n' : 0;
|
||||
index_type x_limit = xr.x_limit;
|
||||
if (strip_trailing_whitespace) {
|
||||
index_type new_limit = limit_without_trailing_whitespace(line, x_limit);
|
||||
@ -2526,7 +2531,7 @@ text_for_range(Screen *self, const Selection *sel, bool insert_newlines, bool st
|
||||
}
|
||||
}
|
||||
}
|
||||
PyObject *text = unicode_in_range(line, xr.x, x_limit, true, leading_char, false);
|
||||
PyObject *text = unicode_in_range(line, xr.x, x_limit, true, insert_newlines && y != limit-1, false);
|
||||
if (text == NULL) { Py_DECREF(ans); return PyErr_NoMemory(); }
|
||||
PyTuple_SET_ITEM(ans, i, text);
|
||||
}
|
||||
@ -2544,12 +2549,12 @@ ansi_for_range(Screen *self, const Selection *sel, bool insert_newlines, bool st
|
||||
ANSIBuf output = {0};
|
||||
const GPUCell *prev_cell = NULL;
|
||||
bool has_escape_codes = false;
|
||||
bool need_newline = false;
|
||||
for (int i = 0, y = idata.y; y < limit; y++, i++) {
|
||||
Line *line = range_line_(self, y);
|
||||
XRange xr = xrange_for_iteration(&idata, y, line);
|
||||
output.len = 0;
|
||||
char_type prefix_char = 0;
|
||||
if (i > 0 && insert_newlines && !line->attrs.continued) prefix_char = '\n';
|
||||
char_type prefix_char = need_newline ? '\n' : 0;
|
||||
index_type x_limit = xr.x_limit;
|
||||
if (strip_trailing_whitespace) {
|
||||
index_type new_limit = limit_without_trailing_whitespace(line, x_limit);
|
||||
@ -2562,6 +2567,7 @@ ansi_for_range(Screen *self, const Selection *sel, bool insert_newlines, bool st
|
||||
}
|
||||
}
|
||||
if (line_as_ansi(line, &output, &prev_cell, xr.x, x_limit, prefix_char)) has_escape_codes = true;
|
||||
need_newline = insert_newlines && !line->gpu_cells[line->xnum-1].attrs.next_char_was_wrapped;
|
||||
PyObject *t = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, output.buf, output.len);
|
||||
if (!t) return NULL;
|
||||
PyTuple_SET_ITEM(ans, i, t);
|
||||
@ -2792,12 +2798,12 @@ 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->as_ansi_buf);
|
||||
return as_text_generic(args, self, get_visual_line, self->lines, &self->as_ansi_buf, false);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
as_text_non_visual(Screen *self, PyObject *args) {
|
||||
return as_text_generic(args, self, get_range_line, self->lines, &self->as_ansi_buf);
|
||||
return as_text_generic(args, self, get_range_line, self->lines, &self->as_ansi_buf, false);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
@ -2807,7 +2813,7 @@ as_text_for_history_buf(Screen *self, PyObject *args) {
|
||||
|
||||
static 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->as_ansi_buf);
|
||||
return as_text_generic(args, self, get_line, self->lines, &self->as_ansi_buf, false);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
@ -2849,7 +2855,7 @@ find_cmd_output(Screen *self, OutputOffset *oo, index_type start_screen_y, unsig
|
||||
found_prompt = true;
|
||||
// change direction to downwards to find command output
|
||||
direction = 1;
|
||||
} else if (line && line->attrs.prompt_kind == OUTPUT_START && !line->attrs.continued) {
|
||||
} else if (line && line->attrs.prompt_kind == OUTPUT_START && !line->attrs.is_continued) {
|
||||
found_output = true; start = y1;
|
||||
found_prompt = true;
|
||||
// keep finding the first output start upwards
|
||||
@ -2863,14 +2869,14 @@ find_cmd_output(Screen *self, OutputOffset *oo, index_type start_screen_y, unsig
|
||||
// find upwards: find prompt after the output, and the first output
|
||||
while (y1 >= upward_limit) {
|
||||
line = checked_range_line(self, y1);
|
||||
if (line && line->attrs.prompt_kind == PROMPT_START && !line->attrs.continued) {
|
||||
if (line && line->attrs.prompt_kind == PROMPT_START && !line->attrs.is_continued) {
|
||||
if (direction == 0) {
|
||||
// find around: stop at prompt start
|
||||
start = y1 + 1;
|
||||
break;
|
||||
}
|
||||
found_next_prompt = true; end = y1;
|
||||
} else if (line && line->attrs.prompt_kind == OUTPUT_START && !line->attrs.continued) {
|
||||
} else if (line && line->attrs.prompt_kind == OUTPUT_START && !line->attrs.is_continued) {
|
||||
start = y1;
|
||||
break;
|
||||
}
|
||||
@ -2941,7 +2947,7 @@ cmd_output(Screen *self, PyObject *args) {
|
||||
bool reached_upper_limit = false;
|
||||
while (!found && !reached_upper_limit) {
|
||||
line = checked_range_line(self, y);
|
||||
if (!line || (line->attrs.prompt_kind == OUTPUT_START && !line->attrs.continued)) {
|
||||
if (!line || (line->attrs.prompt_kind == OUTPUT_START && !line->attrs.is_continued)) {
|
||||
int start = line ? y : y + 1; reached_upper_limit = !line;
|
||||
int y2 = start; unsigned int num_lines = 0;
|
||||
bool found_content = false;
|
||||
@ -2964,7 +2970,7 @@ cmd_output(Screen *self, PyObject *args) {
|
||||
return NULL;
|
||||
}
|
||||
if (found) {
|
||||
DECREF_AFTER_FUNCTION PyObject *ret = as_text_generic(as_text_args, &oo, get_line_from_offset, oo.num_lines, &self->as_ansi_buf);
|
||||
DECREF_AFTER_FUNCTION PyObject *ret = as_text_generic(as_text_args, &oo, get_line_from_offset, oo.num_lines, &self->as_ansi_buf, false);
|
||||
if (!ret) return NULL;
|
||||
}
|
||||
if (oo.reached_upper_limit && self->linebuf == self->main_linebuf && OPT(scrollback_pager_history_size) > 0) Py_RETURN_TRUE;
|
||||
@ -3364,7 +3370,7 @@ screen_selection_range_for_word(Screen *self, const index_type x, const index_ty
|
||||
start = x; end = x;
|
||||
while(true) {
|
||||
while(start > 0 && is_ok(start - 1, false)) start--;
|
||||
if (start > 0 || !line->attrs.continued || *y1 == 0) break;
|
||||
if (start > 0 || !line->attrs.is_continued || *y1 == 0) break;
|
||||
line = visual_line_(self, *y1 - 1);
|
||||
if (!is_ok(self->columns - 1, false)) break;
|
||||
(*y1)--; start = self->columns - 1;
|
||||
@ -3374,7 +3380,7 @@ screen_selection_range_for_word(Screen *self, const index_type x, const index_ty
|
||||
while(end < self->columns - 1 && is_ok(end + 1, true)) end++;
|
||||
if (end < self->columns - 1 || *y2 >= self->lines - 1) break;
|
||||
line = visual_line_(self, *y2 + 1);
|
||||
if (!line->attrs.continued || !is_ok(0, true)) break;
|
||||
if (!line->attrs.is_continued || !is_ok(0, true)) break;
|
||||
(*y2)++; end = 0;
|
||||
}
|
||||
*s = start; *e = end;
|
||||
@ -3542,7 +3548,7 @@ screen_mark_hyperlink(Screen *self, index_type x, index_type y) {
|
||||
|
||||
static index_type
|
||||
continue_line_upwards(Screen *self, index_type top_line, SelectionBoundary *start, SelectionBoundary *end) {
|
||||
while (top_line > 0 && visual_line_(self, top_line)->attrs.continued) {
|
||||
while (top_line > 0 && visual_line_(self, top_line)->attrs.is_continued) {
|
||||
if (!screen_selection_range_for_line(self, top_line - 1, &start->x, &end->x)) break;
|
||||
top_line--;
|
||||
}
|
||||
@ -3551,7 +3557,7 @@ continue_line_upwards(Screen *self, index_type top_line, SelectionBoundary *star
|
||||
|
||||
static index_type
|
||||
continue_line_downwards(Screen *self, index_type bottom_line, SelectionBoundary *start, SelectionBoundary *end) {
|
||||
while (bottom_line < self->lines - 1 && visual_line_(self, bottom_line + 1)->attrs.continued) {
|
||||
while (bottom_line < self->lines - 1 && visual_line_(self, bottom_line + 1)->attrs.is_continued) {
|
||||
if (!screen_selection_range_for_line(self, bottom_line + 1, &start->x, &end->x)) break;
|
||||
bottom_line++;
|
||||
}
|
||||
@ -3971,7 +3977,7 @@ dump_lines_with_attrs(Screen *self, PyObject *accum) {
|
||||
PyObject_CallFunction(accum, "s", "\x1b[33moutput \x1b[39m");
|
||||
break;
|
||||
}
|
||||
if (line->attrs.continued) PyObject_CallFunction(accum, "s", "continued ");
|
||||
if (line->attrs.is_continued) PyObject_CallFunction(accum, "s", "continued ");
|
||||
if (line->attrs.has_dirty_text) PyObject_CallFunction(accum, "s", "dirty ");
|
||||
PyObject_CallFunction(accum, "s", "\n");
|
||||
t = line_as_unicode(line, false);
|
||||
|
@ -278,8 +278,6 @@ def as_text(
|
||||
h: List[str] = [pht] if pht else []
|
||||
screen.as_text_for_history_buf(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'
|
||||
ans = ''.join(chain(h, lines))
|
||||
|
@ -20,11 +20,10 @@
|
||||
def create_lbuf(*lines):
|
||||
maxw = max(map(len, lines))
|
||||
ans = LineBuf(len(lines), maxw)
|
||||
prev_full_length = False
|
||||
for i, l0 in enumerate(lines):
|
||||
ans.line(i).set_text(l0, 0, len(l0), C())
|
||||
ans.set_continued(i, prev_full_length)
|
||||
prev_full_length = len(l0) == maxw
|
||||
if i > 0:
|
||||
ans.set_continued(i, len(lines[i-1]) == maxw)
|
||||
return ans
|
||||
|
||||
|
||||
@ -73,9 +72,9 @@ def test_linebuf(self):
|
||||
self.assertFalse(c.reverse)
|
||||
self.assertTrue(c.bold)
|
||||
self.assertFalse(old.is_continued(0))
|
||||
old.set_continued(0, True)
|
||||
self.assertTrue(old.is_continued(0))
|
||||
self.assertFalse(old.is_continued(1))
|
||||
old.set_continued(1, True)
|
||||
self.assertTrue(old.is_continued(1))
|
||||
self.assertFalse(old.is_continued(0))
|
||||
|
||||
lb = filled_line_buf(5, 5, filled_cursor())
|
||||
lb2 = LineBuf(5, 5)
|
||||
@ -518,7 +517,7 @@ def test_ansi_repr(self):
|
||||
self.ae(l2.as_ansi(), '\x1b[1;2;3;7;9;34;48:2:1:2:3;58:5:5m' '1'
|
||||
'\x1b[22;23;27;29;39;49;59m' '0000')
|
||||
lb = filled_line_buf()
|
||||
for i in range(lb.ynum):
|
||||
for i in range(1, lb.ynum + 1):
|
||||
lb.set_continued(i, True)
|
||||
a = []
|
||||
lb.as_ansi(a.append)
|
||||
|
@ -210,22 +210,23 @@ def init():
|
||||
s.reset_dirty()
|
||||
s.cursor.x, s.cursor.y = 2, 1
|
||||
s.cursor.bold = True
|
||||
self.ae(continuations(s), (True, True, True, True, False))
|
||||
|
||||
def all_lines(s):
|
||||
return tuple(str(s.line(i)) for i in range(s.lines))
|
||||
|
||||
def continuations(s):
|
||||
return tuple(s.line(i).is_continued() for i in range(s.lines))
|
||||
return tuple(s.line(i).last_char_has_wrapped_flag() for i in range(s.lines))
|
||||
|
||||
init()
|
||||
s.erase_in_display(0)
|
||||
self.ae(all_lines(s), ('12345', '12', '', '', ''))
|
||||
self.ae(continuations(s), (False, True, False, False, False))
|
||||
self.ae(continuations(s), (True, False, False, False, False))
|
||||
|
||||
init()
|
||||
s.erase_in_display(1)
|
||||
self.ae(all_lines(s), ('', ' 45', '12345', '12345', '12345'))
|
||||
self.ae(continuations(s), (False, False, True, True, True))
|
||||
self.ae(continuations(s), (False, True, True, True, False))
|
||||
|
||||
init()
|
||||
s.erase_in_display(2)
|
||||
@ -547,16 +548,20 @@ def test_selection_as_text(self):
|
||||
s.draw(str(i) * s.columns)
|
||||
s.start_selection(0, 0)
|
||||
s.update_selection(4, 4)
|
||||
expected = ('55555', '\n66666', '\n77777', '\n88888', '\n99999')
|
||||
self.ae(s.text_for_selection(), expected)
|
||||
|
||||
def ts(*args):
|
||||
return ''.join(s.text_for_selection(*args))
|
||||
|
||||
expected = ''.join(('55555', '\n66666', '\n77777', '\n88888', '\n99999'))
|
||||
self.ae(ts(), expected)
|
||||
s.scroll(2, True)
|
||||
self.ae(s.text_for_selection(), expected)
|
||||
self.ae(ts(), expected)
|
||||
s.reset()
|
||||
s.draw('ab cd')
|
||||
s.start_selection(0, 0)
|
||||
s.update_selection(1, 3)
|
||||
self.ae(s.text_for_selection(), ('ab ', 'cd'))
|
||||
self.ae(s.text_for_selection(False, True), ('ab', 'cd'))
|
||||
self.ae(ts(), ''.join(('ab ', 'cd')))
|
||||
self.ae(ts(False, True), ''.join(('ab', 'cd')))
|
||||
s.reset()
|
||||
s.draw('ab cd')
|
||||
s.start_selection(0, 0)
|
||||
@ -630,6 +635,22 @@ def set_link(url=None, id=None):
|
||||
s.draw('bcdef')
|
||||
self.ae(as_text(s, True), '\x1b[ma\x1b]8;;moo\x1b\\bcde\x1b[mf\n\n\n\x1b]8;;\x1b\\')
|
||||
|
||||
def test_wrapping_serialization(self):
|
||||
from kitty.window import as_text
|
||||
s = self.create_screen(cols=2, lines=2, scrollback=2, options={'scrollback_pager_history_size': 128})
|
||||
s.draw('aabbccddeeff')
|
||||
self.ae(as_text(s, add_history=True), 'aabbccddeeff')
|
||||
self.assertNotIn('\n', as_text(s, add_history=True, as_ansi=True))
|
||||
s = self.create_screen(cols=2, lines=2, scrollback=2, options={'scrollback_pager_history_size': 128})
|
||||
s.draw('1'), s.carriage_return(), s.linefeed()
|
||||
s.draw('2'), s.carriage_return(), s.linefeed()
|
||||
s.draw('3'), s.carriage_return(), s.linefeed()
|
||||
s.draw('4'), s.carriage_return(), s.linefeed()
|
||||
s.draw('5'), s.carriage_return(), s.linefeed()
|
||||
s.draw('6'), s.carriage_return(), s.linefeed()
|
||||
s.draw('7')
|
||||
self.ae(as_text(s, add_history=True), '1\n2\n3\n4\n5\n6\n7')
|
||||
|
||||
def test_pagerhist(self):
|
||||
hsz = 8
|
||||
s = self.create_screen(cols=2, lines=2, scrollback=2, options={'scrollback_pager_history_size': hsz})
|
||||
@ -666,17 +687,17 @@ def test():
|
||||
s = self.create_screen(options={'scrollback_pager_history_size': 2048})
|
||||
text = '\x1b[msoft\r\x1b[mbreak\nnext😼cat'
|
||||
w(text)
|
||||
self.ae(contents(), text + '\n')
|
||||
self.ae(contents(), text)
|
||||
s.historybuf.pagerhist_rewrap(2)
|
||||
self.ae(contents(), '\x1b[mso\rft\x1b[m\rbr\rea\rk\nne\rxt\r😼\rca\rt\n')
|
||||
self.ae(contents(), '\x1b[mso\rft\x1b[m\rbr\rea\rk\nne\rxt\r😼\rca\rt')
|
||||
|
||||
s = self.create_screen(options={'scrollback_pager_history_size': 8})
|
||||
w('😼')
|
||||
self.ae(contents(), '😼\n')
|
||||
self.ae(contents(), '😼')
|
||||
w('abcd')
|
||||
self.ae(contents(), '😼abcd\n')
|
||||
self.ae(contents(), '😼abcd')
|
||||
w('e')
|
||||
self.ae(contents(), 'abcde\n')
|
||||
self.ae(contents(), 'abcde')
|
||||
|
||||
def test_user_marking(self):
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user