diff --git a/kitty/history.c b/kitty/history.c index 9d3040c4a..81e8275e0 100644 --- a/kitty/history.c +++ b/kitty/history.c @@ -185,6 +185,19 @@ as_ansi(HistoryBuf *self, PyObject *callback) { Py_RETURN_NONE; } +static PyObject* +dirty_lines(HistoryBuf *self) { +#define dirty_lines_doc "dirty_lines() -> Line numbers of all lines that have dirty text." + PyObject *ans = PyList_New(0); + for (index_type i = 0; i < self->ynum; i++) { + if (self->line_attrs[i] & TEXT_DIRTY_MASK) { + PyList_Append(ans, PyLong_FromUnsignedLong(i)); + } + } + return ans; +} + + // Boilerplate {{{ static PyObject* rewrap(HistoryBuf *self, PyObject *args); #define rewrap_doc "" @@ -193,6 +206,7 @@ static PyMethodDef methods[] = { METHOD(change_num_of_lines, METH_O) METHOD(line, METH_O) METHOD(as_ansi, METH_O) + METHOD(dirty_lines, METH_NOARGS) METHOD(push, METH_VARARGS) METHOD(rewrap, METH_VARARGS) {NULL, NULL, 0, NULL} /* Sentinel */ @@ -248,7 +262,10 @@ void historybuf_rewrap(HistoryBuf *self, HistoryBuf *other) { return; } other->count = 0; other->start_of_data = 0; - if (self->count > 0) rewrap_inner(self, other, self->count, NULL); + if (self->count > 0) { + rewrap_inner(self, other, self->count, NULL); + for (index_type i = 0; i < other->count; i++) other->line_attrs[(other->start_of_data + i) % other->ynum] |= TEXT_DIRTY_MASK; + } } static PyObject* diff --git a/kitty/line-buf.c b/kitty/line-buf.c index b5c4ade75..0445a6d48 100644 --- a/kitty/line-buf.c +++ b/kitty/line-buf.c @@ -35,6 +35,16 @@ linebuf_clear(LineBuf *self, char_type ch) { } } +void +linebuf_mark_line_dirty(LineBuf *self, index_type y) { + self->line_attrs[y] |= TEXT_DIRTY_MASK; +} + +void +linebuf_mark_line_clean(LineBuf *self, index_type y) { + self->line_attrs[y] &= ~TEXT_DIRTY_MASK; +} + static PyObject* clear(LineBuf *self) { #define clear_doc "Clear all lines in this LineBuf" @@ -156,6 +166,18 @@ set_continued(LineBuf *self, PyObject *args) { Py_RETURN_NONE; } +static PyObject* +dirty_lines(LineBuf *self) { +#define dirty_lines_doc "dirty_lines() -> Line numbers of all lines that have dirty text." + PyObject *ans = PyList_New(0); + for (index_type i = 0; i < self->ynum; i++) { + if (self->line_attrs[i] & TEXT_DIRTY_MASK) { + PyList_Append(ans, PyLong_FromUnsignedLong(i)); + } + } + return ans; +} + static inline bool allocate_line_storage(Line *line, bool initialize) { if (initialize) { @@ -215,7 +237,8 @@ clear_line_(Line *l, index_type xnum) { l->has_dirty_text = false; } -void linebuf_clear_line(LineBuf *self, index_type y) { +void +linebuf_clear_line(LineBuf *self, index_type y) { Line l; init_line(self, &l, self->line_map[y]); clear_line_(&l, self->xnum); @@ -231,7 +254,8 @@ clear_line(LineBuf *self, PyObject *val) { Py_RETURN_NONE; } -void linebuf_index(LineBuf* self, index_type top, index_type bottom) { +void +linebuf_index(LineBuf* self, index_type top, index_type bottom) { if (top >= self->ynum - 1 || bottom >= self->ynum || bottom <= top) return; index_type old_top = self->line_map[top]; line_attrs_type old_attrs = self->line_attrs[top]; @@ -252,7 +276,8 @@ index(LineBuf *self, PyObject *args) { Py_RETURN_NONE; } -void linebuf_reverse_index(LineBuf *self, index_type top, index_type bottom) { +void +linebuf_reverse_index(LineBuf *self, index_type top, index_type bottom) { if (top >= self->ynum - 1 || bottom >= self->ynum || bottom <= top) return; index_type old_bottom = self->line_map[bottom]; line_attrs_type old_attrs = self->line_attrs[bottom]; @@ -297,7 +322,7 @@ 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] = 0; + if (y + num < self->ynum) self->line_attrs[y + num] &= ~CONTINUED_MASK; for (i = 0; i < num; i++) { self->line_map[y + i] = self->scratch[ylimit - num + i]; } @@ -332,7 +357,7 @@ 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] = 0; + self->line_attrs[y] &= ~CONTINUED_MASK; for (i = 0; i < num; i++) { self->line_map[ylimit - num + i] = self->scratch[y + i]; } @@ -346,7 +371,7 @@ linebuf_delete_lines(LineBuf *self, index_type num, index_type y, index_type bot static PyObject* delete_lines(LineBuf *self, PyObject *args) { -#define delete_lines_doc "delete_lines(num, y, bottom) -> Delete num blank lines at y, only changing lines in the range [y, bottom]." +#define delete_lines_doc "delete_lines(num, y, bottom) -> Delete num lines at y, only changing lines in the range [y, bottom]." unsigned int y, num, bottom; if (!PyArg_ParseTuple(args, "III", &num, &y, &bottom)) return NULL; linebuf_delete_lines(self, num, y, bottom); @@ -409,6 +434,7 @@ static PyMethodDef methods[] = { METHOD(as_ansi, METH_O) METHOD(set_attribute, METH_VARARGS) METHOD(set_continued, METH_VARARGS) + METHOD(dirty_lines, METH_NOARGS) METHOD(index, METH_VARARGS) METHOD(reverse_index, METH_VARARGS) METHOD(insert_lines, METH_VARARGS) @@ -491,6 +517,7 @@ linebuf_rewrap(LineBuf *self, LineBuf *other, index_type *num_content_lines_befo rewrap_inner(self, other, first + 1, historybuf); *num_content_lines_after = other->line->ynum + 1; + for (i = 0; i < *num_content_lines_after; i++) other->line_attrs[i] |= TEXT_DIRTY_MASK; *num_content_lines_before = first + 1; } diff --git a/kitty/lineops.h b/kitty/lineops.h index 546b9f644..f76f42000 100644 --- a/kitty/lineops.h +++ b/kitty/lineops.h @@ -67,6 +67,8 @@ void linebuf_insert_lines(LineBuf *self, unsigned int num, unsigned int y, unsig void linebuf_delete_lines(LineBuf *self, index_type num, index_type y, index_type bottom); void linebuf_set_attribute(LineBuf *, unsigned int , unsigned int ); void linebuf_rewrap(LineBuf *self, LineBuf *other, index_type *, index_type *, HistoryBuf *); +void linebuf_mark_line_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_refresh_sprite_positions(LineBuf *self); bool historybuf_resize(HistoryBuf *self, index_type lines); diff --git a/kitty/screen.c b/kitty/screen.c index 72c1a15c6..e4e67b866 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -292,15 +292,18 @@ screen_draw(Screen *self, uint32_t och) { self->cursor->x++; } self->is_dirty = true; + linebuf_mark_line_dirty(self->linebuf, self->cursor->y); } else if (is_combining_char(ch)) { if (self->cursor->x > 0) { linebuf_init_line(self->linebuf, self->cursor->y); line_add_combining_char(self->linebuf->line, ch, self->cursor->x - 1); self->is_dirty = true; + linebuf_mark_line_dirty(self->linebuf, self->cursor->y); } else if (self->cursor->y > 0) { linebuf_init_line(self->linebuf, self->cursor->y - 1); line_add_combining_char(self->linebuf->line, ch, self->columns - 1); self->is_dirty = true; + linebuf_mark_line_dirty(self->linebuf, self->cursor->y); } } } @@ -324,6 +327,7 @@ screen_alignment_display(Screen *self) { for (unsigned int y = 0; y < self->linebuf->ynum; y++) { linebuf_init_line(self->linebuf, y); line_clear_text(self->linebuf->line, 0, self->linebuf->xnum, 'E'); + linebuf_mark_line_dirty(self->linebuf, y); } } @@ -820,7 +824,8 @@ screen_cursor_to_line(Screen *self, unsigned int line) { // Editing {{{ -void screen_erase_in_line(Screen *self, unsigned int how, bool private) { +void +screen_erase_in_line(Screen *self, unsigned int how, bool private) { /*Erases a line in a specific way. :param int how: defines the way the line should be erased in: @@ -856,10 +861,12 @@ void screen_erase_in_line(Screen *self, unsigned int how, bool private) { line_apply_cursor(self->linebuf->line, self->cursor, s, n, true); } self->is_dirty = true; + linebuf_mark_line_dirty(self->linebuf, self->cursor->y); } } -void screen_erase_in_display(Screen *self, unsigned int how, bool private) { +void +screen_erase_in_display(Screen *self, unsigned int how, bool private) { /* Erases display in a specific way. :param int how: defines the way the line should be erased in: @@ -892,6 +899,7 @@ void screen_erase_in_display(Screen *self, unsigned int how, bool private) { } else { line_apply_cursor(self->linebuf->line, self->cursor, 0, self->columns, true); } + linebuf_mark_line_dirty(self->linebuf, i); } self->is_dirty = true; } @@ -900,7 +908,8 @@ void screen_erase_in_display(Screen *self, unsigned int how, bool private) { } } -void screen_insert_lines(Screen *self, unsigned int count) { +void +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) { @@ -910,7 +919,8 @@ void screen_insert_lines(Screen *self, unsigned int count) { } } -void screen_delete_lines(Screen *self, unsigned int count) { +void +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) { @@ -920,7 +930,8 @@ void screen_delete_lines(Screen *self, unsigned int count) { } } -void screen_insert_characters(Screen *self, unsigned int count) { +void +screen_insert_characters(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) { @@ -929,11 +940,13 @@ void screen_insert_characters(Screen *self, unsigned int count) { linebuf_init_line(self->linebuf, self->cursor->y); line_right_shift(self->linebuf->line, x, num); line_apply_cursor(self->linebuf->line, self->cursor, x, num, true); + linebuf_mark_line_dirty(self->linebuf, self->cursor->y); self->is_dirty = true; } } -void screen_delete_characters(Screen *self, unsigned int count) { +void +screen_delete_characters(Screen *self, unsigned int count) { // Delete characters, later characters are moved left unsigned int top = self->margin_top, bottom = self->margin_bottom; if (count == 0) count = 1; @@ -943,17 +956,20 @@ void screen_delete_characters(Screen *self, unsigned int count) { linebuf_init_line(self->linebuf, self->cursor->y); left_shift_line(self->linebuf->line, x, num); line_apply_cursor(self->linebuf->line, self->cursor, self->columns - num, num, true); + linebuf_mark_line_dirty(self->linebuf, self->cursor->y); self->is_dirty = true; } } -void screen_erase_characters(Screen *self, unsigned int count) { +void +screen_erase_characters(Screen *self, unsigned int count) { // Delete characters replacing them by spaces if (count == 0) count = 1; unsigned int x = self->cursor->x; unsigned int num = MIN(self->columns - x, count); linebuf_init_line(self->linebuf, self->cursor->y); line_apply_cursor(self->linebuf->line, self->cursor, x, num, true); + linebuf_mark_line_dirty(self->linebuf, self->cursor->y); self->is_dirty = true; } diff --git a/kitty_tests/datatypes.py b/kitty_tests/datatypes.py index 64ffa027d..0dc77d688 100644 --- a/kitty_tests/datatypes.py +++ b/kitty_tests/datatypes.py @@ -278,6 +278,8 @@ class TestDataTypes(BaseTest): self.ae(cy, 3) for i in range(lb2.ynum): self.ae(lb2.line(i), lb.line(i + 2)) + self.assertFalse(lb.dirty_lines()) + self.ae(lb2.dirty_lines(), list(range(lb2.ynum))) def line_comparison(self, buf, *lines): for i, l0 in enumerate(lines): @@ -298,6 +300,7 @@ class TestDataTypes(BaseTest): lb = create_lbuf('0123 ', '56789') lb2 = self.line_comparison_rewrap(lb, '0123 5', '6789', '') self.assertContinued(lb2, False, True) + self.ae(lb2.dirty_lines(), [0, 1]) lb = create_lbuf('12', 'abc') lb2 = self.line_comparison_rewrap(lb, '12', 'abc') @@ -357,6 +360,7 @@ class TestDataTypes(BaseTest): hb.rewrap(hb2) for i in range(hb2.ynum): self.ae(hb2.line(i), hb.line(i)) + self.ae(hb2.dirty_lines(), list(range(hb2.ynum))) hb = filled_history_buf(5, 5) hb2 = HistoryBuf(hb.ynum, hb.xnum * 2) hb.rewrap(hb2) diff --git a/kitty_tests/screen.py b/kitty_tests/screen.py index cb7fdd5ad..1f2040677 100644 --- a/kitty_tests/screen.py +++ b/kitty_tests/screen.py @@ -324,3 +324,41 @@ class TestScreen(BaseTest): s.cursor_visible = False s.toggle_alt_screen() self.assertFalse(s.cursor_visible) + + def test_dirty_lines(self): + s = self.create_screen() + self.assertFalse(s.linebuf.dirty_lines()) + s.draw('a' * (s.columns * 2)) + self.ae(s.linebuf.dirty_lines(), [0, 1]) + self.assertFalse(s.historybuf.dirty_lines()) + while not s.historybuf.count: + s.draw('a' * (s.columns * 2)) + self.ae(s.historybuf.dirty_lines(), list(range(s.historybuf.count))) + self.ae(s.linebuf.dirty_lines(), list(range(s.lines))) + s.cursor.x, s.cursor.y = 0, 1 + s.insert_lines(2) + self.ae(s.linebuf.dirty_lines(), [0, 3, 4]) + s.draw('a' * (s.columns * s.lines)) + self.ae(s.linebuf.dirty_lines(), list(range(s.lines))) + s.cursor.x, s.cursor.y = 0, 1 + s.delete_lines(2) + self.ae(s.linebuf.dirty_lines(), [0, 1, 2]) + + s = self.create_screen() + self.assertFalse(s.linebuf.dirty_lines()) + s.erase_in_line(0, False) + self.ae(s.linebuf.dirty_lines(), [0]) + s.index(), s.index() + s.erase_in_display(0, False) + self.ae(s.linebuf.dirty_lines(), [0, 2, 3, 4]) + + s = self.create_screen() + self.assertFalse(s.linebuf.dirty_lines()) + s.insert_characters(2) + self.ae(s.linebuf.dirty_lines(), [0]) + s.cursor.y = 1 + s.delete_characters(2) + self.ae(s.linebuf.dirty_lines(), [0, 1]) + s.cursor.y = 2 + s.erase_characters(2) + self.ae(s.linebuf.dirty_lines(), [0, 1, 2])