From 97449dfddbb1ca181f2d7fe7e1e7985258536ffd Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 30 Dec 2024 15:14:04 +0530 Subject: [PATCH] Cursor tracking tests Also fix extra x movement for tracked cursors --- kitty/line.h | 1 - kitty/rewrap.c | 24 +++++++++++++----------- kitty/screen.c | 6 +++--- kitty_tests/multicell.py | 40 +++++++++++++++++++++++++++++++--------- kitty_tests/screen.py | 9 ++++----- 5 files changed, 51 insertions(+), 29 deletions(-) diff --git a/kitty/line.h b/kitty/line.h index aff9d25c5..06d80d5ba 100644 --- a/kitty/line.h +++ b/kitty/line.h @@ -11,7 +11,6 @@ // TODO: Handle selection with multicell // TODO: URL detection with multicell -// TODO: Handle restitch of multiline chars typedef union CellAttrs { struct { diff --git a/kitty/rewrap.c b/kitty/rewrap.c index 4311467f5..94b770565 100644 --- a/kitty/rewrap.c +++ b/kitty/rewrap.c @@ -155,16 +155,15 @@ init_src_line(Rewrap *r) { } static void -update_tracked_cursors(Rewrap *r, index_type num_cells, index_type y, index_type x_limit) { +update_tracked_cursors(Rewrap *r, index_type num_cells, index_type src_y, index_type dest_y, index_type x_limit) { for (TrackCursor *t = r->cursors; !t->is_sentinel; t++) { - if (t->y == y && r->src_x <= t->x && (t->x < r->src_x + num_cells || t->x >= x_limit)) { + if (t->y == src_y && r->src_x <= t->x && (t->x < r->src_x + num_cells || t->x >= x_limit)) { if (r->current_dest_line_is_last_history_line) { t->dest_x = 0; t->dest_y = 0; } else { - index_type x = t->x; - if (x >= x_limit) x = MAX(1u, x_limit) - 1; - t->dest_y = r->dest_y; - t->dest_x = r->dest_x + (x - r->src_x + (x > 0)); + t->dest_y = dest_y; + t->dest_x = r->dest_x + (t->x - r->src_x); + if (t->dest_x > r->dest_xnum) t->dest_x = r->dest_xnum; } } } @@ -197,7 +196,7 @@ copy_multiline_extra_lines(Rewrap *r, CPUCell *src_cell, index_type mc_width) { linebuf_init_line_at(r->scratch, i - 1, &r->dest_scratch); linebuf_mark_line_dirty(r->scratch, i - 1); copy_range(&r->src_scratch, r->src_x, &r->dest_scratch, r->dest_x, mc_width); - update_tracked_cursors(r, mc_width, r->src_y + i, r->src_xnum + 10000 /* ensure cursor is moved only if in region being copied */); + update_tracked_cursors(r, mc_width, r->src_y + i, r->dest_y + i, r->src_xnum + 10000 /* ensure cursor is moved only if in region being copied */); } } @@ -209,15 +208,18 @@ multiline_copy_src_to_dest(Rewrap *r) { c = &r->src.cpu_cells[r->src_x]; if (c->is_multicell) { mc_width = mcd_x_limit(c); - if (c->y || mc_width > r->dest_xnum) { - update_tracked_cursors(r, mc_width, r->src_y, r->src_x_limit); + if (mc_width > r->dest_xnum) { + update_tracked_cursors(r, mc_width, r->src_y, r->dest_y, r->src_x_limit); + r->src_x += mc_width; + continue; + } else if (c->y) { r->src_x += mc_width; continue; } } else mc_width = 1; find_space_in_dest(r, mc_width); copy_range(&r->src, r->src_x, &r->dest, r->dest_x, mc_width); - update_tracked_cursors(r, mc_width, r->src_y, r->src_x_limit); + update_tracked_cursors(r, mc_width, r->src_y, r->dest_y, r->src_x_limit); if (c->scale > 1) copy_multiline_extra_lines(r, c, mc_width); r->src_x += mc_width; r->dest_x += mc_width; } @@ -249,7 +251,7 @@ fast_copy_src_to_dest(Rewrap *r) { } } copy_range(&r->src, r->src_x, &r->dest, r->dest_x, num); - update_tracked_cursors(r, num, r->src_y, r->src_x_limit); + update_tracked_cursors(r, num, r->src_y, r->dest_y, r->src_x_limit); r->src_x += num; r->dest_x += num; } } diff --git a/kitty/screen.c b/kitty/screen.c index d018d3068..8d485b841 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -217,8 +217,8 @@ screen_dirty_sprite_positions(Screen *self) { } static HistoryBuf* -realloc_hb(HistoryBuf *old, unsigned int lines, unsigned int columns, ANSIBuf *as_ansi_buf) { - HistoryBuf *ans = alloc_historybuf(lines, columns, 0, old->text_cache); +realloc_hb(HistoryBuf *old, unsigned int columns, ANSIBuf *as_ansi_buf) { + HistoryBuf *ans = alloc_historybuf(old->ynum, columns, 0, old->text_cache); if (ans == NULL) { PyErr_NoMemory(); return NULL; } ans->pagerhist = old->pagerhist; old->pagerhist = NULL; historybuf_rewrap(old, ans, as_ansi_buf); @@ -397,7 +397,7 @@ screen_resize(Screen *self, unsigned int lines, unsigned int columns) { // Resize main linebuf bool history_buf_last_line_is_split = history_buf_endswith_wrap(self->historybuf); - HistoryBuf *nh = realloc_hb(self->historybuf, self->historybuf->ynum, columns, &self->as_ansi_buf); + HistoryBuf *nh = realloc_hb(self->historybuf, columns, &self->as_ansi_buf); if (nh == NULL) return false; Py_CLEAR(self->historybuf); self->historybuf = nh; RAII_PyObject(prompt_copy, NULL); diff --git a/kitty_tests/multicell.py b/kitty_tests/multicell.py index b99bb3b94..f3b79abfc 100644 --- a/kitty_tests/multicell.py +++ b/kitty_tests/multicell.py @@ -466,6 +466,12 @@ def test_multicell(self: TestMulticell) -> None: s.resize(*o) s.reset() + def mc(x=None, y=None): + if x is not None: + s.cursor.x = x + if y is not None: + s.cursor.y = y + reset() multicell(s, 'a', scale=2) before = as_ansi() @@ -474,27 +480,43 @@ def test_multicell(self: TestMulticell) -> None: reset() s.draw('a' * (s.columns - 2) + '😛' + 'bb') + mc(4, 0) s.resize(s.lines, s.columns-1) self.ae('\x1b[maaaa\x1b[m😛bb', as_ansi().rstrip()) + assert_cursor_at(0, 1) reset() s.draw('a' * (s.columns - 2) + '😛' + 'bb') + mc(0, 1) s.resize(s.lines, s.columns-2) + assert_cursor_at(2, 1) self.ae('\x1b[maaaa\x1b[m😛bb', as_ansi().rstrip()) reset() s.draw('a' * (s.columns - 2) + '😛' + 'bb') + mc(5, 0) s.resize(s.lines, s.columns-3) self.ae('\x1b[maaa\x1b[ma😛\x1b[mbb', as_ansi().rstrip()) # ]]]]]]] + assert_cursor_at(2, 1) + + def resize(lines, cols, cursorx=None, cursory=None): + mc(cursorx, cursory) + before = s.cursor.x, s.cursor.y + cell = s.cpu_cells(s.cursor.y, s.cursor.x) + cell.pop('next_char_was_wrapped') + s.resize(lines, cols) + ncell = s.cpu_cells(s.cursor.y, s.cursor.x) + ncell.pop('next_char_was_wrapped') + self.ae(cell, ncell, f'Cursor moved from: {before} to {(s.cursor.x, s.cursor.y)}') reset() multicell(s, 'a', scale=3), s.draw('b'*(s.columns-3)) - s.resize(s.lines, s.columns-1) + resize(s.lines, s.columns-1, 5, 0) self.ae('\x1b[m\x1b]66;w=1:s=3;a\x07bb\x1b[mb', as_ansi().rstrip()) # ]] ac(0, 0, is_multicell=True) ac(0, 1, is_multicell=True) ac(3, 1, is_multicell=False, text='b') reset() s.draw('X'), multicell(s, 'a', scale=3), s.draw('12345') - s.resize(s.lines, s.columns-1) + resize(s.lines, s.columns-1, 4, 0) self.ae('\x1b[mX\x1b]66;w=1:s=3;a\x071\x1b[m23\x1b[m45', as_ansi().rstrip()) # ]] for y in (0, 1): ac(0, y, is_multicell=False), ac(1, y, is_multicell=True), ac(3, y, is_multicell=True) @@ -502,17 +524,17 @@ def test_multicell(self: TestMulticell) -> None: reset() s.draw('a'*(s.columns - 2)), s.draw('😛'), s.linefeed(), s.carriage_return(), s.draw('123') - s.resize(s.lines, s.columns-1) + resize(s.lines, s.columns-1, 5, 0) self.ae('\x1b[maaaa\x1b[m😛\n\x1b[m123', as_ansi().rstrip()) # ]]]]]]] reset() s.draw('a'*(s.columns - 1)), s.draw('😛'), s.draw('bcd') - s.resize(s.lines, s.columns + 1) + resize(s.lines, s.columns + 1, 0, 1) self.ae('\x1b[maaaaa😛\x1b[mbcd', as_ansi().rstrip()) # ]]]]]]] reset() s.draw('a'*s.columns), s.draw('😛'), s.draw('bcd') - s.resize(s.lines, s.columns + 1) + resize(s.lines, s.columns + 1, 0, 1) self.ae('\x1b[maaaaaa\x1b[m😛bcd', as_ansi().rstrip()) # ]]]]]]] ac(s.columns-1, 0, next_char_was_wrapped=True) s.resize(s.lines, s.columns + 1) @@ -520,7 +542,7 @@ def test_multicell(self: TestMulticell) -> None: reset() s.draw('a'*(s.columns - 1)), multicell(s, 'X', scale=2), s.draw('bcd') - s.resize(s.lines, s.columns + 1) + resize(s.lines, s.columns + 1, 0, 2) self.ae('\x1b[maaaaa\x1b]66;w=1:s=2;X\x07\x1b[mbcd', as_ansi().rstrip()) # ]]]]]]] for y in (0, 1): for x in (1, 2): @@ -539,13 +561,13 @@ def test_multicell(self: TestMulticell) -> None: reset() multicell(s, 'X', scale=4), s.draw('abc') - s.resize(3, 3) + resize(3, 3, 5, 0) self.ae('\x1b[mabc', as_ansi().rstrip()) # ]]]]]]] reset() multicell(s, 'X', width=4), s.draw('abc') - s.resize(3, 3) + resize(3, 3, 4, 0) self.ae('\x1b[mabc', as_ansi().rstrip()) # ]]]]]]] reset() s.draw('1'), multicell(s, 'X', width=4), s.draw('abc') - s.resize(3, 3) + resize(3, 3, 5, 0) self.ae('\x1b[m1ab\x1b[mc', as_ansi().rstrip()) # ]]]]]]] diff --git a/kitty_tests/screen.py b/kitty_tests/screen.py index 31357b117..865cd8ff6 100644 --- a/kitty_tests/screen.py +++ b/kitty_tests/screen.py @@ -302,9 +302,8 @@ class TestScreen(BaseTest): self.assertTrue(s.historybuf.endswith_wrap()) self.ae(str(s.historybuf), '111122') self.ae(at(), text + '\n') - # for some reason rewrap_inner moves the cursor by one cell to the right - self.ae((s.cursor.x, s.cursor.y), (4, 0)) - self.ae(ac(), 'c') + self.ae((s.cursor.x, s.cursor.y), (3, 0)) + self.ae(ac(), 'b') s = self.create_screen(cols=4, lines=4, scrollback=4) s.draw('1111222'), s.linefeed(), s.carriage_return() s.draw('333344445555') @@ -324,10 +323,10 @@ class TestScreen(BaseTest): s.cursor.x, s.cursor.y = 1, 1 self.ae(ac(), 'b') s.resize(s.lines, s.columns * 2) - self.ae(ac(), 'c') + self.ae(ac(), 'b') self.ae(str(s.historybuf), '11112222') self.ae(at(), text + '\n\n') - self.ae((s.cursor.x, s.cursor.y), (2, 0)) + self.ae((s.cursor.x, s.cursor.y), (1, 0)) # test that trailing blank line is preserved on resize s = self.create_screen(cols=5, lines=5, scrollback=15)