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:
Kovid Goyal 2022-12-26 20:26:21 +05:30
parent 4556f5b8f1
commit 68cf9f7514
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
10 changed files with 156 additions and 91 deletions

View File

@ -158,6 +158,7 @@ typedef union CellAttrs {
uint16_t strike : 1; uint16_t strike : 1;
uint16_t dim : 1; uint16_t dim : 1;
uint16_t mark : 2; uint16_t mark : 2;
uint16_t next_char_was_wrapped : 1;
}; };
uint16_t val; uint16_t val;
} CellAttrs; } CellAttrs;
@ -165,7 +166,7 @@ typedef union CellAttrs {
#define WIDTH_MASK (3u) #define WIDTH_MASK (3u)
#define DECORATION_MASK (7u) #define DECORATION_MASK (7u)
#define NUM_UNDERLINE_STYLES (5u) #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 { typedef struct {
color_type fg, bg, decoration_fg; 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 enum { UNKNOWN_PROMPT_KIND = 0, PROMPT_START = 1, SECONDARY_PROMPT = 2, OUTPUT_START = 3 } PromptKind;
typedef union LineAttrs { typedef union LineAttrs {
struct { struct {
uint8_t continued : 1; uint8_t is_continued : 1;
uint8_t has_dirty_text : 1; uint8_t has_dirty_text : 1;
PromptKind prompt_kind : 2; PromptKind prompt_kind : 2;
}; };

View File

@ -164,6 +164,16 @@ init_line(HistoryBuf *self, index_type num, Line *l) {
l->cpu_cells = cpu_lineptr(self, num); l->cpu_cells = cpu_lineptr(self, num);
l->gpu_cells = gpu_lineptr(self, num); l->gpu_cells = gpu_lineptr(self, num);
l->attrs = *attrptr(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 void
@ -171,6 +181,11 @@ historybuf_init_line(HistoryBuf *self, index_type lnum, Line *l) {
init_line(self, index_of(self, lnum), 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* CPUCell*
historybuf_cpu_cells(HistoryBuf *self, index_type lnum) { historybuf_cpu_cells(HistoryBuf *self, index_type lnum) {
return cpu_lineptr(self, index_of(self, 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}; Line l = {.xnum=self->xnum};
init_line(self, self->start_of_data, &l); init_line(self, self->start_of_data, &l);
line_as_ansi(&l, as_ansi_buf, &prev_cell, 0, l.xnum, 0); 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); 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 static index_type
@ -275,6 +294,13 @@ historybuf_pop_line(HistoryBuf *self, Line *line) {
return true; 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* static PyObject*
line(HistoryBuf *self, PyObject *val) { 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" #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}; ANSIBuf output = {0};
for(unsigned int i = 0; i < self->count; i++) { for(unsigned int i = 0; i < self->count; i++) {
init_line(self, i, &l); 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); 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); 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); PyObject *ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, output.buf, output.len);
if (ans == NULL) { PyErr_NoMemory(); goto end; } 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); pagerhist_ensure_start_is_valid_utf8(ph);
if (ph->rewrap_needed) pagerhist_rewrap_to(self, self->xnum); 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); size_t sz = ringbuf_bytes_used(ph->ringbuf);
if (!l.attrs.continued) sz += 1;
PyObject *ans = PyBytes_FromStringAndSize(NULL, sz); PyObject *ans = PyBytes_FromStringAndSize(NULL, sz);
if (!ans) return NULL; if (!ans) return NULL;
uint8_t *buf = (uint8_t*)PyBytes_AS_STRING(ans); uint8_t *buf = (uint8_t*)PyBytes_AS_STRING(ans);
ringbuf_memcpy_from(buf, ph->ringbuf, sz); ringbuf_memcpy_from(buf, ph->ringbuf, sz);
if (!l.attrs.continued) buf[sz-1] = '\n';
if (upto_output_start) { if (upto_output_start) {
const uint8_t *p = reverse_find(buf, sz, (const uint8_t*)"\x1b]133;C\x1b\\"); const uint8_t *p = reverse_find(buf, sz, (const uint8_t*)"\x1b]133;C\x1b\\");
if (p) { if (p) {
@ -484,7 +504,7 @@ PyObject*
as_text_history_buf(HistoryBuf *self, PyObject *args, ANSIBuf *output) { as_text_history_buf(HistoryBuf *self, PyObject *args, ANSIBuf *output) {
GetLineWrapper glw = {.self=self}; GetLineWrapper glw = {.self=self};
glw.line.xnum = self->xnum; 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; 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 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) { 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 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 first_dest_line next_dest_line(false); #define first_dest_line next_dest_line(false);

View File

@ -130,6 +130,7 @@ linebuf_init_line(LineBuf *self, index_type idx) {
self->line->ynum = idx; self->line->ynum = idx;
self->line->xnum = self->xnum; self->line->xnum = self->xnum;
self->line->attrs = self->line_attrs[idx]; 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]); 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; 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* static PyObject*
set_attribute(LineBuf *self, PyObject *args) { set_attribute(LineBuf *self, PyObject *args) {
#define set_attribute_doc "set_attribute(which, val) -> Set the attribute on all cells in the line." #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; unsigned int y;
int val; int val;
if (!PyArg_ParseTuple(args, "Ip", &y, &val)) return NULL; if (!PyArg_ParseTuple(args, "Ip", &y, &val)) return NULL;
if (y >= self->ynum) { PyErr_SetString(PyExc_ValueError, "Out of bounds."); return NULL; } if (y > self->ynum || y < 1) { PyErr_SetString(PyExc_ValueError, "Out of bounds."); return NULL; }
self->line_attrs[y].continued = val; linebuf_set_last_char_as_continuation(self, y-1, val);
Py_RETURN_NONE; 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" #define is_continued_doc "is_continued(y) -> Whether the line y is continued or not"
unsigned long y = PyLong_AsUnsignedLong(val); unsigned long y = PyLong_AsUnsignedLong(val);
if (y >= self->ynum) { PyErr_SetString(PyExc_ValueError, "Out of bounds."); return NULL; } 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; 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_map[i] = self->line_map[i - num];
self->line_attrs[i] = self->line_attrs[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++) { for (i = 0; i < num; i++) {
self->line_map[y + i] = self->scratch[ylimit - 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_map[i] = self->line_map[i + num];
self->line_attrs[i] = self->line_attrs[i + num]; self->line_attrs[i] = self->line_attrs[i + num];
} }
self->line_attrs[y].continued = false;
for (i = 0; i < num; i++) { for (i = 0; i < num; i++) {
self->line_map[ylimit - num + i] = self->scratch[y + i]; self->line_map[ylimit - num + i] = self->scratch[y + i];
} }
@ -414,10 +426,10 @@ as_ansi(LineBuf *self, PyObject *callback) {
} while(ylimit > 0); } while(ylimit > 0);
for(index_type i = 0; i <= ylimit; i++) { 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]); init_line(self, (&l), self->line_map[i]);
line_as_ansi(&l, &output, &prev_cell, 0, l.xnum, 0); 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); ensure_space_for(&output, buf, Py_UCS4, output.len + 1, capacity, 2048, false);
output.buf[output.len++] = 10; // 10 = \n output.buf[output.len++] = 10; // 10 = \n
} }
@ -444,7 +456,7 @@ get_line(void *x, int y) {
static PyObject* static PyObject*
as_text(LineBuf *self, PyObject *args) { as_text(LineBuf *self, PyObject *args) {
ANSIBuf output = {0}; 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); free(output.buf);
return ans; return ans;
} }

View File

@ -229,10 +229,9 @@ cell_as_utf8_for_fallback(CPUCell *cell, char *buf) {
PyObject* 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; size_t n = 0;
static Py_UCS4 buf[4096]; static Py_UCS4 buf[4096];
if (leading_char) buf[n++] = leading_char;
char_type previous_width = 0; char_type previous_width = 0;
for(index_type i = start; i < limit && n < arraysz(buf) - 2 - arraysz(self->cpu_cells->cc_idx); i++) { 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; 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; 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); return PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, n);
} }
PyObject * PyObject *
line_as_unicode(Line* self, bool skip_zero_cells) { 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* static PyObject*
@ -401,11 +403,10 @@ as_ansi(Line* self, PyObject *a UNUSED) {
} }
static PyObject* static PyObject*
is_continued(Line* self, PyObject *a UNUSED) { last_char_has_wrapped_flag(Line* self, PyObject *a UNUSED) {
#define is_continued_doc "Return the line's continued flag" #define last_char_has_wrapped_flag_doc "Return True if the last cell of this line has the wrapped flags set"
PyObject *ans = self->attrs.continued ? Py_True : Py_False; if (self->gpu_cells[self->xnum - 1].attrs.next_char_was_wrapped) { Py_RETURN_TRUE; }
Py_INCREF(ans); Py_RETURN_FALSE;
return ans;
} }
static PyObject* static PyObject*
@ -839,7 +840,7 @@ mark_text_in_line(PyObject *marker, Line *line) {
} }
PyObject* 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(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); } #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; 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; if (nl == NULL || cr == NULL || sgr_reset == NULL) return NULL;
const GPUCell *prev_cell = NULL; const GPUCell *prev_cell = NULL;
ansibuf->active_hyperlink_id = 0; ansibuf->active_hyperlink_id = 0;
bool need_newline = false;
for (index_type y = 0; y < lines; y++) { for (index_type y = 0; y < lines; y++) {
Line *line = get_line(container, y); Line *line = get_line(container, y);
if (!line) { if (PyErr_Occurred()) return NULL; break; } if (!line) { if (PyErr_Occurred()) return NULL; break; }
if (!line->attrs.continued && y > 0) APPEND(nl); if (need_newline) APPEND(nl);
if (as_ansi) { if (as_ansi) {
// less has a bug where it resets colors when it sees a \r, so work // 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 // 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); APPEND_AND_DECREF(t);
if (insert_wrap_markers) APPEND(cr); 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) { if (ansibuf->active_hyperlink_id) {
ansibuf->active_hyperlink_id = 0; ansibuf->active_hyperlink_id = 0;
t = PyUnicode_FromString("\x1b]8;;\x1b\\"); t = PyUnicode_FromString("\x1b]8;;\x1b\\");
@ -919,7 +923,7 @@ static PyMethodDef methods[] = {
METHOD(set_char, METH_VARARGS) METHOD(set_char, METH_VARARGS)
METHOD(set_attribute, METH_VARARGS) METHOD(set_attribute, METH_VARARGS)
METHOD(as_ansi, METH_NOARGS) METHOD(as_ansi, METH_NOARGS)
METHOD(is_continued, METH_NOARGS) METHOD(last_char_has_wrapped_flag, METH_NOARGS)
METHOD(hyperlink_ids, METH_NOARGS) METHOD(hyperlink_ids, METH_NOARGS)
METHOD(width, METH_O) METHOD(width, METH_O)
METHOD(url_start_at, METH_O) METHOD(url_start_at, METH_O)

View File

@ -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_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(CPUCell *cell, bool include_cc, char *buf, char_type);
size_t cell_as_utf8_for_fallback(CPUCell *cell, char *buf); 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); PyObject* line_as_unicode(Line *, bool);
void linebuf_init_line(LineBuf *, index_type); 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_clear_attrs_and_dirty(LineBuf *self, index_type y);
void linebuf_mark_line_clean(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); 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 linebuf_refresh_sprite_positions(LineBuf *self);
void historybuf_add_line(HistoryBuf *self, const Line *line, ANSIBuf*); void historybuf_add_line(HistoryBuf *self, const Line *line, ANSIBuf*);
bool historybuf_pop_line(HistoryBuf *, Line *); bool historybuf_pop_line(HistoryBuf *, Line *);
void historybuf_rewrap(HistoryBuf *self, HistoryBuf *other, ANSIBuf*); void historybuf_rewrap(HistoryBuf *self, HistoryBuf *other, ANSIBuf*);
void historybuf_init_line(HistoryBuf *self, index_type num, Line *l); 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); CPUCell* historybuf_cpu_cells(HistoryBuf *self, index_type num);
void historybuf_mark_line_clean(HistoryBuf *self, index_type y); void historybuf_mark_line_clean(HistoryBuf *self, index_type y);
void historybuf_mark_line_dirty(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 historybuf_clear(HistoryBuf *self);
void mark_text_in_line(PyObject *marker, Line *line); void mark_text_in_line(PyObject *marker, Line *line);
bool line_has_mark(Line *, uint16_t mark); 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); bool colors_for_cell(Line *self, ColorProfile *cp, index_type *x, color_type *fg, color_type *bg);

View File

@ -15,14 +15,15 @@
#define init_src_line(src_y) linebuf_init_line(src, src_y); #define init_src_line(src_y) linebuf_init_line(src, src_y);
#endif #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 #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 #endif
#ifndef next_dest_line #ifndef next_dest_line
#define next_dest_line(continued) \ #define next_dest_line(continued) \
linebuf_set_last_char_as_continuation(dest, dest_y, continued); \
if (dest_y >= dest->ynum - 1) { \ if (dest_y >= dest->ynum - 1) { \
linebuf_index(dest, 0, dest->ynum - 1); \ linebuf_index(dest, 0, dest->ynum - 1); \
if (historybuf != NULL) { \ if (historybuf != NULL) { \
@ -33,11 +34,11 @@
linebuf_clear_line(dest, dest->ynum - 1, true); \ linebuf_clear_line(dest, dest->ynum - 1, true); \
} else dest_y++; \ } else dest_y++; \
linebuf_init_line(dest, dest_y); \ linebuf_init_line(dest, dest_y); \
set_dest_line_attrs(dest_y, continued); set_dest_line_attrs(dest_y);
#endif #endif
#ifndef is_src_line_continued #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 #endif
static inline void static inline void
@ -54,7 +55,7 @@ typedef struct TrackCursor {
static void static void
rewrap_inner(BufType *src, BufType *dest, const index_type src_limit, HistoryBuf UNUSED *historybuf, TrackCursor *track, ANSIBuf *as_ansi_buf) { 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; 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 }; TrackCursor tc_end = {.is_sentinel = true };
if (!track) track = &tc_end; if (!track) track = &tc_end;
@ -62,11 +63,13 @@ rewrap_inner(BufType *src, BufType *dest, const index_type src_limit, HistoryBuf
do { do {
for (TrackCursor *t = track; !t->is_sentinel; t++) t->is_tracked_line = src_y == t->y; for (TrackCursor *t = track; !t->is_sentinel; t++) t->is_tracked_line = src_y == t->y;
init_src_line(src_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; src_x_limit = src->xnum;
if (!src_line_is_continued) { if (!src_line_is_continued) {
// Trim trailing blanks since there is a hard line break at the end of this line // 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--; 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++) { 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; if (t->is_tracked_line && t->x >= src_x_limit) t->x = MAX(1u, src_x_limit) - 1;

View File

@ -317,7 +317,6 @@ found:
// so when resizing, simply blank all lines after the current // so when resizing, simply blank all lines after the current
// prompt and trust the shell to redraw them. // prompt and trust the shell to redraw them.
for (; y < (int)self->main_linebuf->ynum; y++) { 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_clear_line(self->main_linebuf, y, false);
linebuf_init_line(self->main_linebuf, y); linebuf_init_line(self->main_linebuf, y);
if (y <= (int)self->cursor->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); line_clear_text(self->linebuf->line, xpos, 1, BLANK_CHAR);
if (self->modes.mDECAWM) { // overflow goes onto next line 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_carriage_return(self);
screen_linefeed(self); screen_linefeed(self);
self->linebuf->line_attrs[self->cursor->y].continued = true;
linebuf_init_line(self->linebuf, self->cursor->y); linebuf_init_line(self->linebuf, self->cursor->y);
dest_cpu = self->linebuf->line->cpu_cells; dest_cpu = self->linebuf->line->cpu_cells;
dest_gpu = self->linebuf->line->gpu_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 (from_input_stream) self->last_graphic_char = ch;
if (UNLIKELY(self->columns - self->cursor->x < (unsigned int)char_width)) { if (UNLIKELY(self->columns - self->cursor->x < (unsigned int)char_width)) {
if (self->modes.mDECAWM) { if (self->modes.mDECAWM) {
linebuf_set_last_char_as_continuation(self->linebuf, self->cursor->y, true);
screen_carriage_return(self); screen_carriage_return(self);
screen_linefeed(self); screen_linefeed(self);
self->linebuf->line_attrs[self->cursor->y].continued = true;
} else { } else {
self->cursor->x = self->columns - char_width; 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; if (ol.ynum >= self->lines || ol.xnum >= self->columns || !ol.xnum) return NULL;
Line *line = range_line_(self, ol.ynum); Line *line = range_line_(self, ol.ynum);
if (!line) return NULL; 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 #undef ol
} }
@ -1368,7 +1367,6 @@ screen_linefeed(Screen *self) {
bool in_margins = cursor_within_margins(self); bool in_margins = cursor_within_margins(self);
screen_index(self); screen_index(self);
if (self->modes.mLNM) screen_carriage_return(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); 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); linebuf_init_line(self->linebuf, i);
if (private) { if (private) {
line_clear_text(self->linebuf->line, 0, self->columns, BLANK_CHAR); line_clear_text(self->linebuf->line, 0, self->columns, BLANK_CHAR);
linebuf_set_last_char_as_continuation(self->linebuf, i, false);
} else { } else {
line_apply_cursor(self->linebuf->line, self->cursor, 0, self->columns, true); 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); 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* static Line*
visual_line_(Screen *self, int y_) { visual_line_(Screen *self, int y_) {
index_type y = MAX(0, y_); index_type y = MAX(0, y_);
@ -2322,8 +2330,7 @@ visual_line_(Screen *self, int y_) {
} }
y -= self->scrolled_by; y -= self->scrolled_by;
} }
linebuf_init_line(self->linebuf, y); return init_line(self, y);
return self->linebuf->line;
} }
static Line* static Line*
@ -2332,8 +2339,7 @@ range_line_(Screen *self, int y) {
historybuf_init_line(self->historybuf, -(y + 1), self->historybuf->line); historybuf_init_line(self->historybuf, -(y + 1), self->historybuf->line);
return self->historybuf->line; return self->historybuf->line;
} }
linebuf_init_line(self->linebuf, y); return init_line(self, y);
return self->linebuf->line;
} }
static Line* 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++) { for (int i = 0, y = idata.y; y < limit; y++, i++) {
Line *line = range_line_(self, y); Line *line = range_line_(self, y);
XRange xr = xrange_for_iteration(&idata, y, line); 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; index_type x_limit = xr.x_limit;
if (strip_trailing_whitespace) { 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);
@ -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(); } if (text == NULL) { Py_DECREF(ans); return PyErr_NoMemory(); }
PyTuple_SET_ITEM(ans, i, text); 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}; ANSIBuf output = {0};
const GPUCell *prev_cell = NULL; const GPUCell *prev_cell = NULL;
bool has_escape_codes = false; bool has_escape_codes = false;
bool need_newline = false;
for (int i = 0, y = idata.y; y < limit; y++, i++) { for (int i = 0, y = idata.y; y < limit; y++, i++) {
Line *line = range_line_(self, y); Line *line = range_line_(self, y);
XRange xr = xrange_for_iteration(&idata, y, line); XRange xr = xrange_for_iteration(&idata, y, line);
output.len = 0; output.len = 0;
char_type prefix_char = 0; char_type prefix_char = need_newline ? '\n' : 0;
if (i > 0 && insert_newlines && !line->attrs.continued) prefix_char = '\n';
index_type x_limit = xr.x_limit; index_type x_limit = xr.x_limit;
if (strip_trailing_whitespace) { 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);
@ -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; 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); PyObject *t = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, output.buf, output.len);
if (!t) return NULL; if (!t) return NULL;
PyTuple_SET_ITEM(ans, i, t); 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* static PyObject*
as_text(Screen *self, PyObject *args) { 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* static PyObject*
as_text_non_visual(Screen *self, PyObject *args) { 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* static PyObject*
@ -2807,7 +2813,7 @@ as_text_for_history_buf(Screen *self, PyObject *args) {
static PyObject* static PyObject*
as_text_generic_wrapper(Screen *self, PyObject *args, get_line_func get_line) { 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* static PyObject*
@ -2849,7 +2855,7 @@ find_cmd_output(Screen *self, OutputOffset *oo, index_type start_screen_y, unsig
found_prompt = true; found_prompt = true;
// change direction to downwards to find command output // change direction to downwards to find command output
direction = 1; 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_output = true; start = y1;
found_prompt = true; found_prompt = true;
// keep finding the first output start upwards // 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 // find upwards: find prompt after the output, and the first output
while (y1 >= upward_limit) { while (y1 >= upward_limit) {
line = checked_range_line(self, y1); 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) { if (direction == 0) {
// find around: stop at prompt start // find around: stop at prompt start
start = y1 + 1; start = y1 + 1;
break; break;
} }
found_next_prompt = true; end = y1; 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; start = y1;
break; break;
} }
@ -2941,7 +2947,7 @@ cmd_output(Screen *self, PyObject *args) {
bool reached_upper_limit = false; bool reached_upper_limit = false;
while (!found && !reached_upper_limit) { while (!found && !reached_upper_limit) {
line = checked_range_line(self, y); 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 start = line ? y : y + 1; reached_upper_limit = !line;
int y2 = start; unsigned int num_lines = 0; int y2 = start; unsigned int num_lines = 0;
bool found_content = false; bool found_content = false;
@ -2964,7 +2970,7 @@ cmd_output(Screen *self, PyObject *args) {
return NULL; return NULL;
} }
if (found) { 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 (!ret) return NULL;
} }
if (oo.reached_upper_limit && self->linebuf == self->main_linebuf && OPT(scrollback_pager_history_size) > 0) Py_RETURN_TRUE; 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; start = x; end = x;
while(true) { while(true) {
while(start > 0 && is_ok(start - 1, false)) start--; 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); line = visual_line_(self, *y1 - 1);
if (!is_ok(self->columns - 1, false)) break; if (!is_ok(self->columns - 1, false)) break;
(*y1)--; start = self->columns - 1; (*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++; while(end < self->columns - 1 && is_ok(end + 1, true)) end++;
if (end < self->columns - 1 || *y2 >= self->lines - 1) break; if (end < self->columns - 1 || *y2 >= self->lines - 1) break;
line = visual_line_(self, *y2 + 1); 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; (*y2)++; end = 0;
} }
*s = start; *e = end; *s = start; *e = end;
@ -3542,7 +3548,7 @@ screen_mark_hyperlink(Screen *self, index_type x, index_type y) {
static index_type static index_type
continue_line_upwards(Screen *self, index_type top_line, SelectionBoundary *start, SelectionBoundary *end) { 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; if (!screen_selection_range_for_line(self, top_line - 1, &start->x, &end->x)) break;
top_line--; top_line--;
} }
@ -3551,7 +3557,7 @@ continue_line_upwards(Screen *self, index_type top_line, SelectionBoundary *star
static index_type static index_type
continue_line_downwards(Screen *self, index_type bottom_line, SelectionBoundary *start, SelectionBoundary *end) { 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; if (!screen_selection_range_for_line(self, bottom_line + 1, &start->x, &end->x)) break;
bottom_line++; bottom_line++;
} }
@ -3971,7 +3977,7 @@ dump_lines_with_attrs(Screen *self, PyObject *accum) {
PyObject_CallFunction(accum, "s", "\x1b[33moutput \x1b[39m"); PyObject_CallFunction(accum, "s", "\x1b[33moutput \x1b[39m");
break; 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 "); if (line->attrs.has_dirty_text) PyObject_CallFunction(accum, "s", "dirty ");
PyObject_CallFunction(accum, "s", "\n"); PyObject_CallFunction(accum, "s", "\n");
t = line_as_unicode(line, false); t = line_as_unicode(line, false);

View File

@ -278,8 +278,6 @@ def as_text(
h: List[str] = [pht] if pht else [] h: List[str] = [pht] if pht else []
screen.as_text_for_history_buf(h.append, as_ansi, add_wrap_markers) screen.as_text_for_history_buf(h.append, as_ansi, add_wrap_markers)
if h: if h:
if not screen.linebuf.is_continued(0):
h[-1] += '\n'
if as_ansi: if as_ansi:
h[-1] += '\x1b[m' h[-1] += '\x1b[m'
ans = ''.join(chain(h, lines)) ans = ''.join(chain(h, lines))

View File

@ -20,11 +20,10 @@ from . import BaseTest, filled_cursor, filled_history_buf, filled_line_buf
def create_lbuf(*lines): def create_lbuf(*lines):
maxw = max(map(len, lines)) maxw = max(map(len, lines))
ans = LineBuf(len(lines), maxw) ans = LineBuf(len(lines), maxw)
prev_full_length = False
for i, l0 in enumerate(lines): for i, l0 in enumerate(lines):
ans.line(i).set_text(l0, 0, len(l0), C()) ans.line(i).set_text(l0, 0, len(l0), C())
ans.set_continued(i, prev_full_length) if i > 0:
prev_full_length = len(l0) == maxw ans.set_continued(i, len(lines[i-1]) == maxw)
return ans return ans
@ -73,9 +72,9 @@ class TestDataTypes(BaseTest):
self.assertFalse(c.reverse) self.assertFalse(c.reverse)
self.assertTrue(c.bold) self.assertTrue(c.bold)
self.assertFalse(old.is_continued(0)) self.assertFalse(old.is_continued(0))
old.set_continued(0, True) old.set_continued(1, True)
self.assertTrue(old.is_continued(0)) self.assertTrue(old.is_continued(1))
self.assertFalse(old.is_continued(1)) self.assertFalse(old.is_continued(0))
lb = filled_line_buf(5, 5, filled_cursor()) lb = filled_line_buf(5, 5, filled_cursor())
lb2 = LineBuf(5, 5) lb2 = LineBuf(5, 5)
@ -518,7 +517,7 @@ class TestDataTypes(BaseTest):
self.ae(l2.as_ansi(), '\x1b[1;2;3;7;9;34;48:2:1:2:3;58:5:5m' '1' 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') '\x1b[22;23;27;29;39;49;59m' '0000')
lb = filled_line_buf() lb = filled_line_buf()
for i in range(lb.ynum): for i in range(1, lb.ynum + 1):
lb.set_continued(i, True) lb.set_continued(i, True)
a = [] a = []
lb.as_ansi(a.append) lb.as_ansi(a.append)

View File

@ -210,22 +210,23 @@ class TestScreen(BaseTest):
s.reset_dirty() s.reset_dirty()
s.cursor.x, s.cursor.y = 2, 1 s.cursor.x, s.cursor.y = 2, 1
s.cursor.bold = True s.cursor.bold = True
self.ae(continuations(s), (True, True, True, True, False))
def all_lines(s): def all_lines(s):
return tuple(str(s.line(i)) for i in range(s.lines)) return tuple(str(s.line(i)) for i in range(s.lines))
def continuations(s): 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() init()
s.erase_in_display(0) s.erase_in_display(0)
self.ae(all_lines(s), ('12345', '12', '', '', '')) 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() init()
s.erase_in_display(1) s.erase_in_display(1)
self.ae(all_lines(s), ('', ' 45', '12345', '12345', '12345')) 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() init()
s.erase_in_display(2) s.erase_in_display(2)
@ -547,16 +548,20 @@ class TestScreen(BaseTest):
s.draw(str(i) * s.columns) s.draw(str(i) * s.columns)
s.start_selection(0, 0) s.start_selection(0, 0)
s.update_selection(4, 4) 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) s.scroll(2, True)
self.ae(s.text_for_selection(), expected) self.ae(ts(), expected)
s.reset() s.reset()
s.draw('ab cd') s.draw('ab cd')
s.start_selection(0, 0) s.start_selection(0, 0)
s.update_selection(1, 3) s.update_selection(1, 3)
self.ae(s.text_for_selection(), ('ab ', 'cd')) self.ae(ts(), ''.join(('ab ', 'cd')))
self.ae(s.text_for_selection(False, True), ('ab', 'cd')) self.ae(ts(False, True), ''.join(('ab', 'cd')))
s.reset() s.reset()
s.draw('ab cd') s.draw('ab cd')
s.start_selection(0, 0) s.start_selection(0, 0)
@ -630,6 +635,22 @@ class TestScreen(BaseTest):
s.draw('bcdef') s.draw('bcdef')
self.ae(as_text(s, True), '\x1b[ma\x1b]8;;moo\x1b\\bcde\x1b[mf\n\n\n\x1b]8;;\x1b\\') 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): def test_pagerhist(self):
hsz = 8 hsz = 8
s = self.create_screen(cols=2, lines=2, scrollback=2, options={'scrollback_pager_history_size': hsz}) s = self.create_screen(cols=2, lines=2, scrollback=2, options={'scrollback_pager_history_size': hsz})
@ -666,17 +687,17 @@ class TestScreen(BaseTest):
s = self.create_screen(options={'scrollback_pager_history_size': 2048}) s = self.create_screen(options={'scrollback_pager_history_size': 2048})
text = '\x1b[msoft\r\x1b[mbreak\nnext😼cat' text = '\x1b[msoft\r\x1b[mbreak\nnext😼cat'
w(text) w(text)
self.ae(contents(), text + '\n') self.ae(contents(), text)
s.historybuf.pagerhist_rewrap(2) 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}) s = self.create_screen(options={'scrollback_pager_history_size': 8})
w('😼') w('😼')
self.ae(contents(), '😼\n') self.ae(contents(), '😼')
w('abcd') w('abcd')
self.ae(contents(), '😼abcd\n') self.ae(contents(), '😼abcd')
w('e') w('e')
self.ae(contents(), 'abcde\n') self.ae(contents(), 'abcde')
def test_user_marking(self): def test_user_marking(self):