Skip to content

Commit

Permalink
Add multicell tests for various edit operations
Browse files Browse the repository at this point in the history
  • Loading branch information
kovidgoyal committed Nov 19, 2024
1 parent 639ac8a commit b19aa77
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 8 deletions.
5 changes: 2 additions & 3 deletions kitty/line.c
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ text_in_cell_ansi(const CPUCell *c, TextCache *tc, ANSIBuf *output) {
if (c->x || c->y) return 0;
MultiCellData mcd = cell_multicell_data(c, tc);
unsigned n = write_multicell_ansi_prefix(mcd, output);
n += tc_chars_at_index_ansi(tc, c->ch_or_idx, output);
output->buf[output->len] = '\a';
n += tc_chars_at_index_ansi(tc, c->ch_or_idx, output) - 1;
output->buf[output->len - 1] = '\a';
return n;
}
ensure_space_for(output, buf, output->buf[0], output->len + 1, capacity, 2048, false);
Expand Down Expand Up @@ -956,7 +956,6 @@ hyperlink_ids(Line *self, PyObject *args UNUSED) {
return ans;
}


static PyObject *
richcmp(PyObject *obj1, PyObject *obj2, int op);

Expand Down
1 change: 0 additions & 1 deletion kitty/line.h
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,6 @@ cell_first_char(const CPUCell *c, const TextCache *tc) {
return c->ch_or_idx;
}


static inline CellAttrs
cursor_to_attrs(const Cursor *c) {
CellAttrs ans = {
Expand Down
53 changes: 49 additions & 4 deletions kitty/screen.c
Original file line number Diff line number Diff line change
Expand Up @@ -2214,6 +2214,7 @@ screen_erase_in_line(Screen *self, unsigned int how, bool private) {
break;
}
if (n > 0) {
nuke_multicell_char_intersecting_with(self, s, n, self->cursor->y, self->cursor->y + 1, false);
screen_dirty_line_graphics(self, self->cursor->y, self->cursor->y, self->linebuf == self->main_linebuf);
linebuf_init_line(self->linebuf, self->cursor->y);
if (private) {
Expand All @@ -2240,6 +2241,15 @@ screen_clear_scrollback(Screen *self) {
self->scrolled_by = 0;
dirty_scroll(self);
}
LineBuf *orig = self->linebuf; self->linebuf = self->main_linebuf;
CPUCell *cells = linebuf_cpu_cells_for_line(self->linebuf, 0);
for (index_type x = 0; x < self->columns; x++) {
CPUCell *c = cells + x;
if (c->is_multicell && c->y > 0) { // multiline char that extended into scrollback
nuke_multicell_char_at(self, x, 0, false);
}
}
self->linebuf = orig;
}

static Line* visual_line_(Screen *self, int y_);
Expand Down Expand Up @@ -2281,19 +2291,22 @@ screen_erase_in_display(Screen *self, unsigned int how, bool private) {
:param bool private: when ``True`` character attributes are left unchanged
*/
unsigned int a, b;
bool nuke_multicell_chars = true;
switch(how) {
case 0:
a = self->cursor->y + 1; b = self->lines; break;
case 1:
a = 0; b = self->cursor->y; break;
case 22:
screen_move_into_scrollback(self);
nuke_multicell_chars = false; // they have been moved into scrollback and we would get double deletions
how = 2;
/* fallthrough */
case 2:
case 3:
grman_clear(self->grman, how == 3, self->cell_size);
a = 0; b = self->lines; break;
a = 0; b = self->lines; nuke_multicell_chars = false;
break;
default:
return;
}
Expand All @@ -2307,6 +2320,7 @@ screen_erase_in_display(Screen *self, unsigned int how, bool private) {
linebuf_clear_attrs_and_dirty(self->linebuf, i);
}
} else linebuf_clear_lines(self->linebuf, self->cursor, a, b);
if (nuke_multicell_chars) nuke_multicell_char_intersecting_with(self, 0, self->columns, a, b, false);
self->is_dirty = true;
if (selection_intersects_screen_lines(&self->selections, a, b)) clear_selection(&self->selections);
}
Expand All @@ -2324,11 +2338,30 @@ screen_insert_lines(Screen *self, unsigned int count) {
unsigned int top = self->margin_top, bottom = self->margin_bottom;
if (count == 0) count = 1;
if (top <= self->cursor->y && self->cursor->y <= bottom) {
// remove split multiline chars at top edge
CPUCell *cells = linebuf_cpu_cells_for_line(self->linebuf, self->cursor->y);
for (index_type x = 0; x < self->columns; x++) {
if (cells[x].is_multicell && cells[x].y) nuke_multicell_char_at(self, x, self->cursor->y, false);
}
screen_dirty_line_graphics(self, top, bottom, self->linebuf == self->main_linebuf);
linebuf_insert_lines(self->linebuf, count, self->cursor->y, bottom);
self->is_dirty = true;
clear_selection(&self->selections);
screen_carriage_return(self);
// remove split multiline chars at bottom of screen
cells = linebuf_cpu_cells_for_line(self->linebuf, bottom);
for (index_type x = 0; x < self->columns; x++) {
if (cells[x].is_multicell) {
MultiCellData mcd = cell_multicell_data(cells + x, self->text_cache);
index_type y_limit = mcd.scale;
if (cells[x].y + 1u < y_limit) {
index_type orig = self->lines;
self->lines = bottom + 1;
nuke_multicell_char_at(self, x, bottom, false);
self->lines = orig;
}
}
}
}
}

Expand All @@ -2351,6 +2384,11 @@ screen_delete_lines(Screen *self, unsigned int count) {
unsigned int top = self->margin_top, bottom = self->margin_bottom;
if (count == 0) count = 1;
if (top <= self->cursor->y && self->cursor->y <= bottom) {
index_type y = self->cursor->y;
nuke_multiline_char_intersecting_with(self, 0, self->columns, y, y + 1, false);
y += count;
y = MIN(bottom, y);
nuke_multiline_char_intersecting_with(self, 0, self->columns, y, y + 1, false);
screen_dirty_line_graphics(self, top, bottom, self->linebuf == self->main_linebuf);
linebuf_delete_lines(self->linebuf, count, self->cursor->y, bottom);
self->is_dirty = true;
Expand Down Expand Up @@ -5137,16 +5175,23 @@ static PyObject*
cpu_cells(Screen *self, PyObject *args) {
int y, x = -1;
if (!PyArg_ParseTuple(args, "i|i", &y, &x)) return NULL;
if (y < 0 || y >= (int)self->lines) { PyErr_SetString(PyExc_IndexError, "y out of bounds"); return NULL; }
if (y >= (int)self->lines) { PyErr_SetString(PyExc_IndexError, "y out of bounds"); return NULL; }
CPUCell *cells;
if (y >= 0) cells = linebuf_cpu_cells_for_line(self->linebuf, y);
else {
Line *l = self->linebuf == self->main_linebuf ? checked_range_line(self, y) : NULL;
if (!l) { PyErr_SetString(PyExc_IndexError, "y out of bounds"); return NULL; }
cells = l->cpu_cells;
}
if (x > -1) {
if (x >= (int)self->columns) { PyErr_SetString(PyExc_IndexError, "x out of bounds"); return NULL; }
return cpu_cell_as_dict(linebuf_cpu_cell_at(self->linebuf, x, y), self->text_cache, self->lc, self->hyperlink_pool);
return cpu_cell_as_dict(cells + x, self->text_cache, self->lc, self->hyperlink_pool);
}
index_type start_x = 0, x_limit = self->columns;
RAII_PyObject(ans, PyTuple_New(x_limit - start_x));
if (ans) {
for (index_type x = start_x; x < x_limit; x++) {
PyObject *d = cpu_cell_as_dict(linebuf_cpu_cell_at(self->linebuf, x, y), self->text_cache, self->lc, self->hyperlink_pool);
PyObject *d = cpu_cell_as_dict(cells + x, self->text_cache, self->lc, self->hyperlink_pool);
if (!d) return NULL;
PyTuple_SET_ITEM(ans, x, d);
}
Expand Down
73 changes: 73 additions & 0 deletions kitty_tests/multicell.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,3 +253,76 @@ def big_a(x, y):
s.erase_characters(3)
assert_line('\0\0\0\0c\0')
assert_line('\0\0\0\0\0\0', 1)

# Erase in line
for x in (1, 2):
s.reset()
s.draw('a'), multicell(s, 'b', scale=2), s.draw('c')
s.cursor.x = x
s.erase_in_line(0)
assert_line('a\0\0\0\0\0')
s.reset()
s.draw('a'), multicell(s, 'b', width=2), s.draw('c')
s.cursor.x = x
s.erase_in_line(1)
assert_line('\0\0\0c\0\0')
s.reset()
s.draw('a'), multicell(s, 'b', scale=2), s.draw('c')
s.erase_in_line(2)
for y in (0, 1):
assert_line('\0\0\0\0\0\0', y)
s.reset()
s.draw('a'), multicell(s, 'b', scale=2), s.draw('c')
s.cursor.y = 1
s.erase_in_line(2)
assert_line('a\0\0c\0\0', 0)
assert_line('\0\0\0\0\0\0', 1)

# Clear scrollback
s.reset()
s.draw('a'), multicell(s, 'b', scale=2), s.draw('c')
for i in range(s.lines):
s.index()
s.cursor.y = 0
assert_line('\0__\0\0\0')
s.clear_scrollback()
assert_line('\0\0\0\0\0\0')

# Erase in display
for x in (1, 2):
s.reset(), s.draw('a'), multicell(s, 'b', scale=2), s.draw('c')
s.cursor.x = x
s.erase_in_display(0)
assert_line('a\0\0\0\0\0')
s.reset(), s.draw('a'), multicell(s, 'b', scale=2), s.draw('c')
s.cursor.x, s.cursor.y = 2, 1
s.erase_in_display(0)
assert_line('a\0\0c\0\0', 0)
assert_line('\0\0\0\0\0\0', 1)
s.reset(), s.draw('a'), multicell(s, 'b', scale=2), s.draw('c')
for i in range(s.lines):
s.index()
s.erase_in_display(22)
assert_line('ab_c\0\0', -2)
assert_line('\0__\0\0\0', -1)
self.ae(s.historybuf.line(1).as_ansi(), f'a\x1b]{TEXT_SIZE_CODE};s=2;b\x07c')
self.ae(s.historybuf.line(0).as_ansi(), ' ')

# Insert lines
s.reset()
multicell(s, 'a', scale=2)
s.cursor.x, s.cursor.y = 0, s.lines - 2
multicell(s, 'b', scale=2)
s.cursor.x, s.cursor.y = 0, 1
s.insert_lines(1)
for y in range(s.lines):
assert_line('\0' * s.columns, y)

# Delete lines
s.reset()
multicell(s, 'a', scale=2)
s.cursor.y = 1
multicell(s, 'b', scale=2)
s.delete_lines(1)
for y in range(s.lines):
assert_line('\0' * s.columns, y)

0 comments on commit b19aa77

Please sign in to comment.