diff --git a/kitty/cursor.c b/kitty/cursor.c index a25950f3f..249b7b614 100644 --- a/kitty/cursor.c +++ b/kitty/cursor.c @@ -36,15 +36,20 @@ repr(Cursor *self) { ); } +static inline void cursor_reset_display_attrs(Cursor *self) { + self->bg = 0; self->fg = 0; self->decoration_fg = 0; + self->decoration = 0; self->bold = false; self->italic = false; self->reverse = false; self->strikethrough = false; +} + static PyObject * reset_display_attrs(Cursor *self) { #define reset_display_attrs_doc "Reset all display attributes to unset" - self->bg = 0; self->fg = 0; self->decoration_fg = 0; - self->decoration = 0; self->bold = false; self->italic = false; self->reverse = false; self->strikethrough = false; + cursor_reset_display_attrs(self); Py_RETURN_NONE; } void cursor_reset(Cursor *self) { + cursor_reset_display_attrs(self); self->x = 0; self->y = 0; self->shape = 0; self->blink = false; self->color = 0; self->hidden = false; diff --git a/kitty/data-types.h b/kitty/data-types.h index 2ec3bf5d4..5e3a1a32e 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -271,28 +271,33 @@ PyObject* parse_bytes_dump(PyObject UNUSED *, PyObject *); PyObject* parse_bytes(PyObject UNUSED *, PyObject *); uint16_t* translation_table(char); uint32_t decode_utf8(uint32_t*, uint32_t*, uint8_t byte); -void linebuf_init_line(LineBuf *, index_type); +void cursor_reset(Cursor*); +Cursor* cursor_copy(Cursor*); +bool update_cell_range_data(SpriteMap *, Line *, unsigned int, unsigned int, ColorProfile *, const uint32_t, const uint32_t, unsigned int *); +uint32_t to_color(ColorProfile *, uint32_t, uint32_t); + +PyObject* line_text_at(char_type, combining_type); +void line_clear_text(Line *self, unsigned int at, unsigned int num, int ch); +void line_apply_cursor(Line *self, Cursor *cursor, unsigned int at, unsigned int num, bool clear_char); void line_set_char(Line *, unsigned int , uint32_t , unsigned int , Cursor *); void line_right_shift(Line *, unsigned int , unsigned int ); void line_add_combining_char(Line *, uint32_t , unsigned int ); -void cursor_reset(Cursor*); -void linebuf_set_attribute(LineBuf *, unsigned int , unsigned int ); -Cursor* cursor_copy(Cursor*); + +void linebuf_init_line(LineBuf *, index_type); void linebuf_clear(LineBuf *); +void linebuf_init_line(LineBuf *, index_type); +void linebuf_index(LineBuf* self, index_type top, index_type bottom); +void linebuf_clear_line(LineBuf *self, index_type y); +void linebuf_insert_lines(LineBuf *self, unsigned int num, unsigned int y, unsigned int bottom); +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 screen_restore_cursor(Screen *); void screen_save_cursor(Screen *); void screen_cursor_position(Screen*, unsigned int, unsigned int); void screen_erase_in_display(Screen *, unsigned int, bool); void screen_draw(Screen *screen, uint8_t *buf, unsigned int buflen); -bool update_cell_range_data(SpriteMap *, Line *, unsigned int, unsigned int, ColorProfile *, const uint32_t, const uint32_t, unsigned int *); -uint32_t to_color(ColorProfile *, uint32_t, uint32_t); -PyObject* line_text_at(char_type, combining_type); -void linebuf_init_line(LineBuf *, index_type); -void linebuf_index(LineBuf* self, index_type top, index_type bottom); -void linebuf_clear_line(LineBuf *self, index_type y); void screen_ensure_bounds(Screen *self, bool use_margins); -void line_clear_text(Line *self, unsigned int at, unsigned int num, int ch); -void line_apply_cursor(Line *self, Cursor *cursor, unsigned int at, unsigned int num, bool clear_char); void screen_toggle_screen_buffer(Screen *self); void screen_normal_keypad_mode(Screen *self); void screen_alternate_keypad_mode(Screen *self); diff --git a/kitty/line-buf.c b/kitty/line-buf.c index 91919900f..fe9233a27 100644 --- a/kitty/line-buf.c +++ b/kitty/line-buf.c @@ -266,13 +266,9 @@ is_continued(LineBuf *self, PyObject *val) { Py_RETURN_FALSE; } -static PyObject* -insert_lines(LineBuf *self, PyObject *args) { -#define insert_lines_doc "insert_lines(num, y, bottom) -> Insert num blank lines at y, only changing lines in the range [y, bottom]." - unsigned int y, num, bottom; +void linebuf_insert_lines(LineBuf *self, unsigned int num, unsigned int y, unsigned int bottom) { index_type i; - if (!PyArg_ParseTuple(args, "III", &num, &y, &bottom)) return NULL; - if (y >= self->ynum || y > bottom || bottom >= self->ynum) { PyErr_SetString(PyExc_ValueError, "Out of bounds"); return NULL; } + if (y >= self->ynum || y > bottom || bottom >= self->ynum) return; index_type ylimit = bottom + 1; num = MIN(ylimit - y, num); if (num > 0) { @@ -294,12 +290,22 @@ insert_lines(LineBuf *self, PyObject *args) { self->continued_map[i] = 0; } } +} + +static PyObject* +insert_lines(LineBuf *self, PyObject *args) { +#define insert_lines_doc "insert_lines(num, y, bottom) -> Insert num blank 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_insert_lines(self, num, y, bottom); Py_RETURN_NONE; } -static inline void do_delete(LineBuf *self, index_type num, index_type y, index_type bottom) { +void linebuf_delete_lines(LineBuf *self, index_type num, index_type y, index_type bottom) { index_type i; index_type ylimit = bottom + 1; + num = MIN(bottom + 1 - y, num); + if (y >= self->ynum || y > bottom || bottom >= self->ynum || num < 1) return; for (i = y; i < y + num; i++) { self->scratch[i] = self->line_map[i]; } @@ -324,11 +330,7 @@ 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]." unsigned int y, num, bottom; if (!PyArg_ParseTuple(args, "III", &num, &y, &bottom)) return NULL; - if (y >= self->ynum || y > bottom || bottom >= self->ynum) { PyErr_SetString(PyExc_ValueError, "Out of bounds"); return NULL; } - num = MIN(bottom + 1 - y, num); - if (num > 0) { - do_delete(self, num, y, bottom); - } + linebuf_delete_lines(self, num, y, bottom); Py_RETURN_NONE; } diff --git a/kitty/line.c b/kitty/line.c index 4ccc92eed..23636438c 100644 --- a/kitty/line.c +++ b/kitty/line.c @@ -207,12 +207,16 @@ void line_apply_cursor(Line *self, Cursor *cursor, unsigned int at, unsigned int char_type attrs = CURSOR_TO_ATTRS(cursor, 1); color_type col = (cursor->fg & COL_MASK) | ((color_type)(cursor->bg & COL_MASK) << COL_SHIFT); decoration_type dfg = cursor->decoration_fg & COL_MASK; + if (!clear_char) attrs = ((attrs >> ATTRS_SHIFT) & ~WIDTH_MASK) << ATTRS_SHIFT; for (index_type i = at; i < self->xnum && i < at + num; i++) { if (clear_char) { self->chars[i] = 32 | attrs; self->combining_chars[i] = 0; - } else self->chars[i] = (self->chars[i] & CHAR_MASK) | attrs; + } else { + char_type w = ((self->chars[i] >> ATTRS_SHIFT) & WIDTH_MASK) << ATTRS_SHIFT; + self->chars[i] = (self->chars[i] & CHAR_MASK) | attrs | w; + } self->colors[i] = col; self->decoration_fg[i] = dfg; } diff --git a/kitty/screen.c b/kitty/screen.c index 3bf0994c4..288268544 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -34,6 +34,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { self->cursor = alloc_cursor(); self->main_linebuf = alloc_linebuf(lines, columns); self->alt_linebuf = alloc_linebuf(lines, columns); self->linebuf = self->main_linebuf; + // TODO: Change the savepoints objects to use a circular buffer, so there are no mallocs during normal operation self->main_savepoints = PyList_New(0); self->alt_savepoints = PyList_New(0); self->savepoints = self->main_savepoints; self->change_tracker = alloc_change_tracker(lines, columns); @@ -184,19 +185,6 @@ void screen_shift_in(Screen UNUSED *self, uint8_t UNUSED ch) { // TODO: Implement this } -void screen_toggle_screen_buffer(Screen *self) { - screen_save_cursor(self); - if (self->linebuf == self->main_linebuf) { - self->linebuf = self->alt_linebuf; - self->savepoints = self->alt_savepoints; - } else { - self->linebuf = self->main_linebuf; - self->savepoints = self->main_savepoints; - } - screen_restore_cursor(self); - tracker_update_screen(self->change_tracker); -} - // Graphics {{{ void screen_change_default_color(Screen *self, unsigned int which, uint32_t col) { if (self->callbacks == Py_None) return; @@ -210,6 +198,19 @@ void screen_change_default_color(Screen *self, unsigned int which, uint32_t col) // Modes {{{ +void screen_toggle_screen_buffer(Screen *self) { + screen_save_cursor(self); + if (self->linebuf == self->main_linebuf) { + self->linebuf = self->alt_linebuf; + self->savepoints = self->alt_savepoints; + } else { + self->linebuf = self->main_linebuf; + self->savepoints = self->main_savepoints; + } + screen_restore_cursor(self); + tracker_update_screen(self->change_tracker); +} + void screen_normal_keypad_mode(Screen UNUSED *self) {} // Not implemented as this is handled by the GUI void screen_alternate_keypad_mode(Screen UNUSED *self) {} // Not implemented as this is handled by the GUI @@ -517,6 +518,61 @@ void screen_erase_in_display(Screen *self, unsigned int how, bool private) { screen_erase_in_line(self, how, private); } } + +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 <= (unsigned int)self->cursor->y && (unsigned int)self->cursor->y <= bottom) { + linebuf_insert_lines(self->linebuf, count, self->cursor->y, bottom); + tracker_update_line_range(self->change_tracker, self->cursor->y, bottom); + screen_carriage_return(self, 13); + } +} + +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 <= (unsigned int)self->cursor->y && (unsigned int)self->cursor->y <= bottom) { + linebuf_delete_lines(self->linebuf, count, self->cursor->y, bottom); + tracker_update_line_range(self->change_tracker, self->cursor->y, bottom); + screen_carriage_return(self, 13); + } +} + +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 <= (unsigned int)self->cursor->y && (unsigned int)self->cursor->y <= bottom) { + unsigned int x = self->cursor->x; + unsigned int num = MIN(self->columns - x, 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); + tracker_update_cell_range(self->change_tracker, self->cursor->y, x, self->columns - 1); + } +} + +void screen_delete_characters(Screen *self, unsigned int count) { + unsigned int top = self->margin_top, bottom = self->margin_bottom; + if (count == 0) count = 1; + if (top <= (unsigned int)self->cursor->y && (unsigned int)self->cursor->y <= bottom) { + unsigned int x = self->cursor->x; + unsigned int num = MIN(self->columns - x, 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); + tracker_update_cell_range(self->change_tracker, self->cursor->y, x, self->columns - 1); + } +} + +void screen_erase_characters(Screen *self, unsigned int count) { + 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); + tracker_update_cell_range(self->change_tracker, self->cursor->y, x, MIN(x + num, self->columns) - 1); +} // }}} // Python interface {{{ @@ -590,6 +646,40 @@ cursor_back(Screen *self, PyObject *args) { Py_RETURN_NONE; } +static PyObject* +erase_in_line(Screen *self, PyObject *args) { +#define erase_in_line_doc "" + bool private = false; + unsigned int how = 0; + if (!PyArg_ParseTuple(args, "|Ip", &how, &private)) return NULL; + screen_erase_in_line(self, how, private); + Py_RETURN_NONE; +} + +static PyObject* +erase_in_display(Screen *self, PyObject *args) { +#define erase_in_display_doc "" + bool private = false; + unsigned int how = 0; + if (!PyArg_ParseTuple(args, "|Ip", &how, &private)) return NULL; + screen_erase_in_display(self, how, private); + Py_RETURN_NONE; +} + +#define COUNT_WRAP(name) \ + static PyObject* name(Screen *self, PyObject *args) { \ + unsigned int count = 1; \ + if (!PyArg_ParseTuple(args, "|I", &count)) return NULL; \ + screen_##name(self, count); \ + Py_RETURN_NONE; } +COUNT_WRAP(insert_lines) +COUNT_WRAP(delete_lines) +COUNT_WRAP(insert_characters) +COUNT_WRAP(delete_characters) +COUNT_WRAP(erase_characters) + +#define MND(name, args) {#name, (PyCFunction)name, args, ""}, + static PyMethodDef methods[] = { METHOD(line, METH_O) METHOD(draw, METH_VARARGS) @@ -601,6 +691,13 @@ static PyMethodDef methods[] = { METHOD(reset_dirty, METH_NOARGS) METHOD(consolidate_changes, METH_NOARGS) METHOD(cursor_back, METH_VARARGS) + METHOD(erase_in_line, METH_VARARGS) + METHOD(erase_in_display, METH_VARARGS) + MND(insert_lines, METH_VARARGS) + MND(delete_lines, METH_VARARGS) + MND(insert_characters, METH_VARARGS) + MND(delete_characters, METH_VARARGS) + MND(erase_characters, METH_VARARGS) {NULL} /* Sentinel */ }; diff --git a/kitty_tests/screen.py b/kitty_tests/screen.py index 9c550fb9e..789a6f822 100644 --- a/kitty_tests/screen.py +++ b/kitty_tests/screen.py @@ -4,7 +4,7 @@ from . import BaseTest -from kitty.fast_data_types import DECAWM, IRM +from kitty.fast_data_types import DECAWM, IRM, Cursor class TestScreen(BaseTest): @@ -55,7 +55,7 @@ class TestScreen(BaseTest): def test_draw_char(self): # Test in line-wrap, non-insert mode - s = self.create_screen() + s = self.create_screen2() s.draw('ココx'.encode('utf-8')) self.ae(str(s.line(0)), 'ココx') self.ae(tuple(map(s.line(0).width, range(5))), (2, 0, 2, 0, 1)) @@ -103,7 +103,7 @@ class TestScreen(BaseTest): self.assertChanges(s, ignore='cursor', cells={4: ((0, 4),)}) def test_char_manipulation(self): - s = self.create_screen() + s = self.create_screen2() def init(): s.reset(), s.reset_dirty() @@ -128,6 +128,10 @@ class TestScreen(BaseTest): s.insert_characters(1) self.ae(str(s.line(0)), ' xコ ') self.assertChanges(s, ignore='cursor', cells={0: ((0, 4),)}) + c = Cursor() + c.bold = True + s.line(0).apply_cursor(c, 0, 5) + self.ae(s.line(0).width(2), 2) init() s.delete_characters(2) @@ -160,7 +164,7 @@ class TestScreen(BaseTest): self.ae(str(s.line(0)), ' ') self.assertChanges(s, cells={0: ((0, 4),)}) init() - s.erase_in_line(2, private=True) + s.erase_in_line(2, True) self.ae((False, False, False, False, False), tuple(map(lambda i: s.line(0).cursor_from(i).bold, range(5)))) def test_erase_in_screen(self):