From 9a7b4263e09d81a9a0b49cd98d716b16210bcf9a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 13 Nov 2016 14:10:57 +0530 Subject: [PATCH] More work on the screen replacement The first set of tests now pass --- kitty/data-types.c | 2 + kitty/data-types.h | 31 ++-- kitty/line-buf.c | 24 +-- kitty/line.c | 31 ++-- kitty/parser.c | 6 +- kitty/screen.c | 358 ++++++++++++++++++++++++++++++++-------- kitty/screen.py | 1 + kitty/tracker.c | 51 +++--- kitty/tracker.h | 14 ++ kitty_tests/__init__.py | 4 +- kitty_tests/screen.py | 13 +- 11 files changed, 394 insertions(+), 141 deletions(-) diff --git a/kitty/data-types.c b/kitty/data-types.c index 271c100e8..b7ceb2343 100644 --- a/kitty/data-types.c +++ b/kitty/data-types.c @@ -51,6 +51,8 @@ PyInit_fast_data_types(void) { PyModule_AddIntMacro(m, CURSOR_BLOCK); PyModule_AddIntMacro(m, CURSOR_BEAM); PyModule_AddIntMacro(m, CURSOR_UNDERLINE); + PyModule_AddIntMacro(m, DECAWM); + PyModule_AddIntMacro(m, IRM); } return m; diff --git a/kitty/data-types.h b/kitty/data-types.h index 4f2d1b08c..7ac3944bb 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -43,6 +43,8 @@ typedef unsigned int index_type; #define CURSOR_BLOCK 1 #define CURSOR_BEAM 2 #define CURSOR_UNDERLINE 3 +#define FG 1 +#define BG 2 #define CURSOR_TO_ATTRS(c, w) \ ((w) | (((c->decoration & 3) << DECORATION_SHIFT) | ((c->bold & 1) << BOLD_SHIFT) | \ @@ -233,6 +235,7 @@ typedef struct { Cursor *cursor; PyObject *savepoints, *main_savepoints, *alt_savepoints, *callbacks; LineBuf *linebuf, *main_linebuf, *alt_linebuf; + bool *tabstops; ChangeTracker *change_tracker; ScreenModes modes; @@ -243,12 +246,6 @@ typedef struct { } Screen; PyTypeObject Screen_Type; -Line* alloc_line(); -Cursor* alloc_cursor(); -LineBuf* alloc_linebuf(); -ChangeTracker* alloc_change_tracker(); -Savepoint* alloc_savepoint(); - #define left_shift_line(line, at, num) \ for(index_type __i__ = (at); __i__ < (line)->xnum - (num); __i__++) { \ COPY_CELL(line, __i__ + (num), line, __i__) \ @@ -257,6 +254,11 @@ Savepoint* alloc_savepoint(); // Global functions +Line* alloc_line(); +Cursor* alloc_cursor(); +LineBuf* alloc_linebuf(unsigned int, unsigned int); +ChangeTracker* alloc_change_tracker(unsigned int, unsigned int); +Savepoint* alloc_savepoint(); int init_LineBuf(PyObject *); int init_Cursor(PyObject *); int init_Line(PyObject *); @@ -279,14 +281,23 @@ Cursor* cursor_copy(Cursor*); void linebuf_clear(LineBuf *); bool screen_restore_cursor(Screen *); bool screen_save_cursor(Screen *); -bool screen_cursor_position(Screen*, unsigned int, unsigned int); -bool screen_erase_in_display(Screen *, unsigned int, bool); -bool screen_draw(Screen *screen, uint8_t *buf, unsigned int buflen); +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); -#define DECLARE_CH_SCREEN_HANDLER(name) bool screen_##name(Screen *screen, uint8_t ch); +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); +bool screen_toggle_screen_buffer(Screen *self); +void screen_normal_keypad_mode(Screen *self); +void screen_alternate_keypad_mode(Screen *self); +void screen_change_default_color(Screen *self, unsigned int which, uint32_t col); +#define DECLARE_CH_SCREEN_HANDLER(name) void screen_##name(Screen *screen, uint8_t ch); DECLARE_CH_SCREEN_HANDLER(bell) DECLARE_CH_SCREEN_HANDLER(backspace) DECLARE_CH_SCREEN_HANDLER(tab) diff --git a/kitty/line-buf.c b/kitty/line-buf.c index b7dd0e36f..91919900f 100644 --- a/kitty/line-buf.c +++ b/kitty/line-buf.c @@ -33,7 +33,7 @@ clear(LineBuf *self) { static PyObject * new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { LineBuf *self; - index_type xnum, ynum; + unsigned int xnum = 1, ynum = 1; if (!PyArg_ParseTuple(args, "II", &ynum, &xnum)) return NULL; @@ -202,19 +202,23 @@ copy_line_to(LineBuf *self, PyObject *args) { Py_RETURN_NONE; } +void linebuf_clear_line(LineBuf *self, index_type y) { + Line l; + INIT_LINE(self, &l, self->line_map[y]); + CLEAR_LINE(&l, 0, self->xnum); + self->continued_map[y] = 0; +} + static PyObject* clear_line(LineBuf *self, PyObject *val) { #define clear_line_doc "clear_line(y) -> Clear the specified line" index_type y = (index_type)PyLong_AsUnsignedLong(val); - Line l; if (y >= self->ynum) { PyErr_SetString(PyExc_ValueError, "Out of bounds"); return NULL; } - INIT_LINE(self, &l, self->line_map[y]); - CLEAR_LINE(&l, 0, self->xnum); - self->continued_map[y] = 0; + linebuf_clear_line(self, y); Py_RETURN_NONE; } -static inline void index_inner(LineBuf* self, index_type top, index_type bottom) { +void linebuf_index(LineBuf* self, index_type top, index_type bottom) { index_type old_top = self->line_map[top]; bool old_cont = self->continued_map[top]; for (index_type i = top; i < bottom; i++) { @@ -231,7 +235,7 @@ index(LineBuf *self, PyObject *args) { unsigned int top, bottom; if (!PyArg_ParseTuple(args, "II", &top, &bottom)) return NULL; if (top >= self->ynum - 1 || bottom >= self->ynum || bottom <= top) { PyErr_SetString(PyExc_ValueError, "Out of bounds"); return NULL; } - index_inner(self, top, bottom); + linebuf_index(self, top, bottom); Py_RETURN_NONE; } @@ -404,7 +408,7 @@ static inline void copy_range(Line *src, index_type src_at, Line* dest, index_ty #define next_dest_line(continued) {\ if (dest_y >= dest->ynum - 1) { \ - index_inner(dest, 0, dest->ynum - 1); \ + linebuf_index(dest, 0, dest->ynum - 1); \ PyObject *l = create_line_copy_inner(dest, dest_y); \ if (l == NULL) return false; \ if (PyList_Append(extra_lines, l) != 0) { Py_CLEAR(l); return false; } \ @@ -488,6 +492,6 @@ end: return Py_BuildValue("Ni", ret, cursor_y); } -LineBuf *alloc_linebuf() { - return (LineBuf*)new(&LineBuf_Type, NULL, NULL); +LineBuf *alloc_linebuf(unsigned int lines, unsigned int columns) { + return (LineBuf*)new(&LineBuf_Type, Py_BuildValue("II", lines, columns), NULL); } diff --git a/kitty/line.c b/kitty/line.c index 94be0007b..4ccc92eed 100644 --- a/kitty/line.c +++ b/kitty/line.c @@ -185,27 +185,25 @@ cursor_from(Line* self, PyObject *args) { return (PyObject*)ans; } +void line_clear_text(Line *self, unsigned int at, unsigned int num, int ch) { + const char_type repl = ((char_type)ch & CHAR_MASK) | (1 << ATTRS_SHIFT); + for (index_type i = at; i < MIN(self->xnum, at + num); i++) { + self->chars[i] = (self->chars[i] & ATTRS_MASK_WITHOUT_WIDTH) | repl; + } + memset(self->combining_chars + at, 0, MIN(num, self->xnum - at) * sizeof(combining_type)); +} + static PyObject* clear_text(Line* self, PyObject *args) { #define clear_text_doc "clear_text(at, num, ch=' ') -> Clear characters in the specified range, preserving formatting." unsigned int at, num; int ch = 32; if (!PyArg_ParseTuple(args, "II|C", &at, &num, &ch)) return NULL; - const char_type repl = ((char_type)ch & CHAR_MASK) | (1 << ATTRS_SHIFT); - for (index_type i = at; i < MIN(self->xnum, at + num); i++) { - self->chars[i] = (self->chars[i] & ATTRS_MASK_WITHOUT_WIDTH) | repl; - } - memset(self->combining_chars + at, 0, MIN(num, self->xnum - at) * sizeof(combining_type)); + line_clear_text(self, at, num, ch); Py_RETURN_NONE; } -static PyObject* -apply_cursor(Line* self, PyObject *args) { -#define apply_cursor_doc "apply_cursor(cursor, at=0, num=1, clear_char=False) -> Apply the formatting attributes from cursor to the specified characters in this line." - Cursor* cursor; - unsigned int at=0, num=1; - int clear_char = 0; - if (!PyArg_ParseTuple(args, "O!|IIp", &Cursor_Type, &cursor, &at, &num, &clear_char)) return NULL; +void line_apply_cursor(Line *self, Cursor *cursor, unsigned int at, unsigned int num, bool clear_char) { 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; @@ -218,7 +216,16 @@ apply_cursor(Line* self, PyObject *args) { self->colors[i] = col; self->decoration_fg[i] = dfg; } +} +static PyObject* +apply_cursor(Line* self, PyObject *args) { +#define apply_cursor_doc "apply_cursor(cursor, at=0, num=1, clear_char=False) -> Apply the formatting attributes from cursor to the specified characters in this line." + Cursor* cursor; + unsigned int at=0, num=1; + int clear_char = 0; + if (!PyArg_ParseTuple(args, "O!|IIp", &Cursor_Type, &cursor, &at, &num, &clear_char)) return NULL; + line_apply_cursor(self, cursor, at, num, clear_char & 1); Py_RETURN_NONE; } diff --git a/kitty/parser.c b/kitty/parser.c index fd3209f84..2d633621d 100644 --- a/kitty/parser.c +++ b/kitty/parser.c @@ -17,7 +17,6 @@ // Parse text {{{ static inline bool read_text(Screen *screen, uint8_t *buf, unsigned int buflen, unsigned int *pos) { - bool ret; uint8_t ch; while(*pos < buflen) { @@ -25,14 +24,13 @@ read_text(Screen *screen, uint8_t *buf, unsigned int buflen, unsigned int *pos) #define DRAW_TEXT \ if (screen->parser_has_pending_text) { \ screen->parser_has_pending_text = false; \ - ret = screen_draw(screen, buf + screen->parser_text_start, (*pos) - screen->parser_text_start); \ + screen_draw(screen, buf + screen->parser_text_start, (*pos) - screen->parser_text_start); \ screen->parser_text_start = 0; \ - if (!ret) return false; \ } #define CALL_SCREEN_HANDLER(name) \ DRAW_TEXT; \ - if (!screen_##name(screen, ch)) return false; + screen_##name(screen, ch); #define CHANGE_PARSER_STATE(state) screen->parser_state = state; return true; switch(ch) { diff --git a/kitty/screen.c b/kitty/screen.c index 812229be7..d6f99da43 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -32,64 +32,76 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { self->margin_top = 0; self->margin_bottom = self->lines - 1; self->callbacks = callbacks; Py_INCREF(callbacks); self->cursor = alloc_cursor(); - self->main_linebuf = alloc_linebuf(); self->alt_linebuf = alloc_linebuf(); + self->main_linebuf = alloc_linebuf(lines, columns); self->alt_linebuf = alloc_linebuf(lines, columns); self->linebuf = self->main_linebuf; self->main_savepoints = PyList_New(0); self->alt_savepoints = PyList_New(0); self->savepoints = self->main_savepoints; - self->change_tracker = alloc_change_tracker(); - if (self->cursor == NULL || self->main_linebuf == NULL || self->alt_linebuf == NULL || self->main_savepoints == NULL || self->alt_savepoints == NULL || self->change_tracker == NULL) { + self->change_tracker = alloc_change_tracker(lines, columns); + self->tabstops = PyMem_Calloc(self->columns, sizeof(bool)); + if (self->cursor == NULL || self->main_linebuf == NULL || self->alt_linebuf == NULL || self->main_savepoints == NULL || self->alt_savepoints == NULL || self->change_tracker == NULL || self->tabstops == NULL) { Py_CLEAR(self); return NULL; } } return (PyObject*) self; } +bool screen_reset(Screen *self) { + if (self->linebuf == self->alt_linebuf) {if (!screen_toggle_screen_buffer(self)) return false; } + linebuf_clear(self->linebuf); + self->current_charset = 2; + self->g0_charset = translation_table('B'); + self->g1_charset = translation_table('0'); + self->modes = empty_modes; + self->utf8_state = 0; + self->margin_top = 0; self->margin_bottom = self->lines - 1; + // In terminfo we specify the number of initial tabstops (it) as 8 + for (unsigned int t=0; t < self->columns; t++) self->tabstops[t] = t > 0 && (t+1) % 8 == 0; + screen_normal_keypad_mode(self); + cursor_reset(self->cursor); + tracker_cursor_changed(self->change_tracker); + screen_cursor_position(self, 1, 1); + screen_change_default_color(self, FG, 0); + screen_change_default_color(self, BG, 0); + tracker_update_screen(self->change_tracker); + return true; +} + + static void dealloc(Screen* self) { Py_CLEAR(self->callbacks); - Py_CLEAR(self->cursor); Py_CLEAR(self->main_linebuf); Py_CLEAR(self->alt_linebuf); + Py_CLEAR(self->cursor); + Py_CLEAR(self->main_linebuf); + Py_CLEAR(self->alt_linebuf); Py_CLEAR(self->main_savepoints); Py_CLEAR(self->alt_savepoints); Py_CLEAR(self->change_tracker); + PyMem_Free(self->tabstops); Py_TYPE(self)->tp_free((PyObject*)self); } // }}} -bool screen_bell(Screen UNUSED *self, uint8_t ch) { // {{{ +void screen_bell(Screen UNUSED *self, uint8_t ch) { // {{{ FILE *f = fopen("/dev/tty", "w"); if (f != NULL) { fwrite(&ch, 1, 1, f); fclose(f); } - return true; } // }}} - -bool screen_linefeed(Screen UNUSED *self, uint8_t UNUSED ch) { - // TODO: Implement this - return true; -} - -bool screen_carriage_return(Screen UNUSED *self, uint8_t UNUSED ch) { - // TODO: Implement this - return true; -} - - // Draw text {{{ -static inline int safe_wcwidth(uint32_t ch) { +static inline unsigned int safe_wcwidth(uint32_t ch) { int ans = wcwidth(ch); if (ans < 0) ans = 1; return MIN(2, ans); } -static inline bool +static inline void draw_codepoint(Screen UNUSED *self, uint32_t ch) { - if (is_ignored_char(ch)) return true; - int char_width = safe_wcwidth(ch); - int space_left_in_line = self->columns - self->cursor->x; - if (space_left_in_line < char_width) { + if (is_ignored_char(ch)) return; + unsigned int char_width = safe_wcwidth(ch); + if (self->columns - (unsigned int)self->cursor->x < char_width) { if (self->modes.mDECAWM) { - if (!screen_carriage_return(self, 13)) return false; - if (!screen_linefeed(self, 10)) return false; + screen_carriage_return(self, 13); + screen_linefeed(self, 10); self->linebuf->continued_map[self->cursor->y] = true; } else { self->cursor->x = self->columns - char_width; @@ -120,16 +132,15 @@ draw_codepoint(Screen UNUSED *self, uint32_t ch) { tracker_update_cell_range(self->change_tracker, self->cursor->y - 1, self->columns - 1, self->columns - 1); } } - return true; } -static inline bool +static inline void screen_draw_utf8(Screen *self, uint8_t *buf, unsigned int buflen) { uint32_t prev = UTF8_ACCEPT, codepoint = 0; for (unsigned int i = 0; i < buflen; i++, prev = self->utf8_state) { switch (decode_utf8(&self->utf8_state, &codepoint, buf[i])) { case UTF8_ACCEPT: - if (!draw_codepoint(self, codepoint)) return false; + draw_codepoint(self, codepoint); break; case UTF8_REJECT: self->utf8_state = UTF8_ACCEPT; @@ -137,49 +148,41 @@ screen_draw_utf8(Screen *self, uint8_t *buf, unsigned int buflen) { break; } } - return true; } -static inline bool +static inline void screen_draw_charset(Screen *self, unsigned short *table, uint8_t *buf, unsigned int buflen) { for (unsigned int i = 0; i < buflen; i++) { - if (!draw_codepoint(self, table[buf[i]])) return false; + draw_codepoint(self, table[buf[i]]); } - return true; } -bool screen_draw(Screen *self, uint8_t *buf, unsigned int buflen) { +void screen_draw(Screen *self, uint8_t *buf, unsigned int buflen) { switch(self->current_charset) { case 0: - return screen_draw_charset(self, self->g0_charset, buf, buflen); - break; + screen_draw_charset(self, self->g0_charset, buf, buflen); break; case 1: - return screen_draw_charset(self, self->g1_charset, buf, buflen); - break; + screen_draw_charset(self, self->g1_charset, buf, buflen); break; default: - return screen_draw_utf8(self, buf, buflen); break; + screen_draw_utf8(self, buf, buflen); break; } } // }}} -bool screen_backspace(Screen UNUSED *self, uint8_t UNUSED ch) { +void screen_backspace(Screen UNUSED *self, uint8_t UNUSED ch) { // TODO: Implement this - return true; } -bool screen_tab(Screen UNUSED *self, uint8_t UNUSED ch) { +void screen_tab(Screen UNUSED *self, uint8_t UNUSED ch) { // TODO: Implement this - return true; } -bool screen_shift_out(Screen UNUSED *self, uint8_t UNUSED ch) { +void screen_shift_out(Screen UNUSED *self, uint8_t UNUSED ch) { // TODO: Implement this - return true; } -bool screen_shift_in(Screen UNUSED *self, uint8_t UNUSED ch) { +void screen_shift_in(Screen UNUSED *self, uint8_t UNUSED ch) { // TODO: Implement this - return true; } bool screen_toggle_screen_buffer(Screen *self) { @@ -196,9 +199,22 @@ bool screen_toggle_screen_buffer(Screen *self) { return true; } +// Graphics {{{ +void screen_change_default_color(Screen *self, unsigned int which, uint32_t col) { + if (self->callbacks == Py_None) return; + if (col & 0xFF) PyObject_CallMethod(self->callbacks, "change_default_color", "s(III)", which == FG ? "fg" : "bg", + (col >> 24) & 0xFF, (col >> 16) & 0xFF, (col >> 8) & 0xFF); + else PyObject_CallMethod(self->callbacks, "change_default_color", "sO", which == FG ? "fg" : "bg", Py_None); + if (PyErr_Occurred()) PyErr_Print(); + PyErr_Clear(); +} +// }}} // Modes {{{ +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 + static inline void set_mode_from_const(Screen *self, int mode, bool val) { switch(mode) { case LNM: @@ -226,12 +242,12 @@ bool screen_set_mode(Screen *self, int mode) { if (mode == DECCOLM) { // When DECCOLM mode is set, the screen is erased and the cursor // moves to the home position. - if (!screen_erase_in_display(self, 2, false)) return false; - if (!screen_cursor_position(self, 1, 1)) return false; + screen_erase_in_display(self, 2, false); + screen_cursor_position(self, 1, 1); } // According to `vttest`, DECOM should also home the cursor, see // vttest/main.c:303. - if (mode == DECOM) { if (!screen_cursor_position(self, 1, 1)) return false; } + if (mode == DECOM) screen_cursor_position(self, 1, 1); if (mode == DECSCNM) { // Mark all displayed characters as reverse. @@ -273,12 +289,12 @@ bool screen_reset_mode(Screen *self, int mode) { if (mode == DECCOLM) { // When DECCOLM mode is set, the screen is erased and the cursor // moves to the home position. - if (!screen_erase_in_display(self, 2, false)) return false; - if (!screen_cursor_position(self, 1, 1)) return false; + screen_erase_in_display(self, 2, false); + screen_cursor_position(self, 1, 1); } // According to `vttest`, DECOM should also home the cursor, see // vttest/main.c:303. - if (mode == DECOM) { if (!screen_cursor_position(self, 1, 1)) return false; } + if (mode == DECOM) screen_cursor_position(self, 1, 1); if (mode == DECSCNM) { // Mark all displayed characters as reverse. @@ -303,6 +319,68 @@ bool screen_reset_mode(Screen *self, int mode) { // }}} // Cursor {{{ + +void screen_cursor_back(Screen *self, unsigned int count/*=1*/, int move_direction/*=-1*/) { + int x = self->cursor->x; + if (count == 0) count = 1; + self->cursor->x += move_direction * count; + screen_ensure_bounds(self, false); + if (x != self->cursor->x) tracker_cursor_changed(self->change_tracker); +} + +void screen_cursor_forward(Screen *self, unsigned int count/*=1*/) { + screen_cursor_back(self, count, 1); +} + +void screen_cursor_up(Screen *self, unsigned int count/*=1*/, bool do_carriage_return/*=false*/, int move_direction/*=-1*/) { + int x = self->cursor->x, y = self->cursor->y; + if (count == 0) count = 1; + self->cursor->y += move_direction * count; + screen_ensure_bounds(self, true); + if (do_carriage_return) self->cursor->x = 0; + if (x != self->cursor->x || y != self->cursor->y) tracker_cursor_changed(self->change_tracker); +} + +void screen_cursor_up1(Screen *self, unsigned int count/*=1*/) { + screen_cursor_up(self, count, true, -1); +} + +void screen_cursor_down(Screen *self, unsigned int count/*=1*/) { + screen_cursor_up(self, count, false, 1); +} + +void screen_cursor_down1(Screen *self, unsigned int count/*=1*/) { + screen_cursor_up(self, count, true, 1); +} + +void screen_index(Screen *self) { + // Move cursor down one line, scrolling screen if needed + unsigned int top = self->margin_top, bottom = self->margin_bottom; + if ((unsigned int)self->cursor->y == self->margin_bottom) { + linebuf_index(self->linebuf, top, bottom); + if (self->linebuf == self->main_linebuf) { + // TODO: Add line to tophistorybuf + tracker_line_added_to_history(self->change_tracker); + } + linebuf_clear_line(self->linebuf, bottom); + if (bottom - top > self->lines - 1) tracker_update_screen(self->change_tracker); + else tracker_update_line_range(self->change_tracker, top, bottom); + } else screen_cursor_down(self, 1); +} + +void screen_carriage_return(Screen *self, uint8_t UNUSED ch) { + if (self->cursor->x != 0) { + self->cursor->x = 0; + tracker_cursor_changed(self->change_tracker); + } +} + +void screen_linefeed(Screen *self, uint8_t UNUSED ch) { + screen_index(self); + if (self->modes.mLNM) screen_carriage_return(self, 13); + screen_ensure_bounds(self, false); +} + bool screen_save_cursor(Screen *self) { Savepoint *sp = alloc_savepoint(); if (sp == NULL) return false; @@ -333,30 +411,120 @@ bool screen_restore_cursor(Screen *self) { } else { screen_cursor_position(self, 1, 1); tracker_cursor_changed(self->change_tracker); - screen_reset_mode(self, DECOM); + if (!screen_reset_mode(self, DECOM)) return false; } return true; } -bool screen_cursor_position(Screen UNUSED *self, unsigned int UNUSED line, unsigned int UNUSED column) { - return true; // TODO: Implement this +void screen_ensure_bounds(Screen *self, bool use_margins/*=false*/) { + unsigned int top, bottom; + if (use_margins || self->modes.mDECOM) { + top = self->margin_top; bottom = self->margin_bottom; + } else { + top = 0; bottom = self->lines - 1; + } + self->cursor->x = MIN((unsigned int)MAX(0, self->cursor->x), self->columns - 1); + self->cursor->y = MAX(top, MIN((unsigned int)MAX(0, self->cursor->y), bottom)); +} + +void screen_cursor_position(Screen *self, unsigned int line, unsigned int column) { + line = (line || 1) - 1; + column = (column || 1) - 1; + if (self->modes.mDECOM) { + line += self->margin_top; + if (line < self->margin_bottom || line > self->margin_top) return; + } + int x = self->cursor->x, y = self->cursor->y; + self->cursor->x = column; self->cursor->y = line; + screen_ensure_bounds(self, false); + if (x != self->cursor->x || y != self->cursor->y) tracker_cursor_changed(self->change_tracker); } // }}} // Editing {{{ -bool screen_erase_in_display(Screen UNUSED *self, unsigned int UNUSED how, bool UNUSED private) { - return true; // TODO: Implement this + +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: + + * ``0`` -- Erases from cursor to end of line, including cursor + position. + * ``1`` -- Erases from beginning of line to cursor, + including cursor position. + * ``2`` -- Erases complete line. + :param bool private: when ``True`` character attributes are left + unchanged. + */ + unsigned int s, n; + switch(how) { + case 0: + s = self->cursor->x; + n = self->columns - self->cursor->x; + break; + case 1: + s = 0; n = self->cursor->x + 1; + break; + case 2: + s = 0; n = self->columns; + break; + default: + return; + } + if (n > s) { + linebuf_init_line(self->linebuf, self->cursor->y); + if (private) { + line_clear_text(self->linebuf->line, s, n, ' '); + } else { + line_apply_cursor(self->linebuf->line, self->cursor, s, n, true); + } + tracker_update_cell_range(self->change_tracker, self->cursor->y, s, MIN(s+n, self->columns) - 1); + } +} + +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: + + * ``0`` -- Erases from cursor to end of screen, including + cursor position. + * ``1`` -- Erases from beginning of screen to cursor, + including cursor position. + * ``2`` -- Erases complete display. All lines are erased + and changed to single-width. Cursor does not move. + :param bool private: when ``True`` character attributes are left unchanged + */ + unsigned int a, b; + switch(how) { + case 0: + a = self->cursor->y + 1; b = self->lines; break; + case 1: + a = 0; b = self->cursor->y; break; + case 2: + a = 0; b = self->lines; break; + default: + return; + } + if (b > a) { + for (unsigned int i=a; i < b; i++) { + linebuf_init_line(self->linebuf, i); + if (private) { + line_clear_text(self->linebuf->line, 0, self->columns, ' '); + } else { + line_apply_cursor(self->linebuf->line, self->cursor, 0, self->columns, true); + } + } + tracker_update_line_range(self->change_tracker, a, b-1); + } + if (how != 2) { + screen_erase_in_line(self, how, private); + } } // }}} -bool screen_reset(Screen *self) { - if (self->linebuf == self->alt_linebuf) {if (!screen_toggle_screen_buffer(self)) return false; } - linebuf_clear(self->linebuf); - // TODO: Implement this - return true; -} - +// Python interface {{{ static PyObject* line(Screen *self, PyObject *val) { #define line_doc "" @@ -372,17 +540,72 @@ draw(Screen *self, PyObject *args) { #define draw_doc "" Py_buffer pybuf; if(!PyArg_ParseTuple(args, "y*", &pybuf)) return NULL; - if (!screen_draw(self, pybuf.buf, pybuf.len)) return NULL; + screen_draw(self, pybuf.buf, pybuf.len); + Py_RETURN_NONE; +} + +static PyObject* +reset(Screen *self) { +#define reset_doc "" + if(!screen_reset(self)) return NULL; + Py_RETURN_NONE; +} + +static PyObject* +reset_mode(Screen *self, PyObject *args) { +#define reset_mode_doc "" + bool private = false; + unsigned int mode; + if (!PyArg_ParseTuple(args, "I|p", &mode, &private)) return NULL; + if (private) mode <<= 5; + if (!screen_reset_mode(self, mode)) return NULL; Py_RETURN_NONE; } -// Boilerplate {{{ +static PyObject* +set_mode(Screen *self, PyObject *args) { +#define set_mode_doc "" + bool private = false; + unsigned int mode; + if (!PyArg_ParseTuple(args, "I|p", &mode, &private)) return NULL; + if (private) mode <<= 5; + if (!screen_set_mode(self, mode)) return NULL; + Py_RETURN_NONE; +} + +static PyObject* +reset_dirty(Screen *self) { +#define reset_dirty_doc "" + tracker_reset(self->change_tracker); + Py_RETURN_NONE; +} + +static PyObject* +consolidate_changes(Screen *self) { +#define consolidate_changes_doc "" + return tracker_consolidate_changes(self->change_tracker); +} + +static PyObject* +cursor_back(Screen *self, PyObject *args) { +#define cursor_back_doc "" + unsigned int count = 1; + if (!PyArg_ParseTuple(args, "|I", &count)) return NULL; + screen_cursor_back(self, count, -1); + Py_RETURN_NONE; +} static PyMethodDef methods[] = { METHOD(line, METH_O) METHOD(draw, METH_VARARGS) + METHOD(set_mode, METH_VARARGS) + METHOD(reset_mode, METH_VARARGS) METHOD(enable_focus_tracking, METH_NOARGS) METHOD(in_bracketed_paste_mode, METH_NOARGS) + METHOD(reset, METH_NOARGS) + METHOD(reset_dirty, METH_NOARGS) + METHOD(consolidate_changes, METH_NOARGS) + METHOD(cursor_back, METH_VARARGS) {NULL} /* Sentinel */ }; @@ -408,4 +631,3 @@ PyTypeObject Screen_Type = { INIT_TYPE(Screen) // }}} - diff --git a/kitty/screen.py b/kitty/screen.py index 6f87f2925..a7de4e3cf 100644 --- a/kitty/screen.py +++ b/kitty/screen.py @@ -162,6 +162,7 @@ class Screen: """ self.lines, self.columns = lines, columns self.tracker.resize(self.lines, self.columns) + self.tabstops = {x for x in self.tabstops if x < self.columns} # TODO: Implement rewrap for history buf self.tophistorybuf.clear() is_main = self.linebuf is self.main_linebuf diff --git a/kitty/tracker.c b/kitty/tracker.c index b4338c21a..cfb26a3e0 100644 --- a/kitty/tracker.c +++ b/kitty/tracker.c @@ -8,26 +8,27 @@ #include "data-types.h" #include "tracker.h" -#define RESET_STATE_VARS(self) \ - self->screen_changed = false; self->cursor_changed = false; self->dirty = false; self->history_line_added_count = 0; -static PyObject* -resize(ChangeTracker *self, PyObject *args) { -#define resize_doc "Resize theis change tracker must be called when the screen it is tracking for is resized" - unsigned long ynum=1, xnum=1; - if (args) { - if (!PyArg_ParseTuple(args, "kk", &ynum, &xnum)) return NULL; - } - self->ynum = ynum; self->xnum = xnum; +bool tracker_resize(ChangeTracker *self, unsigned int ynum, unsigned int xnum) { #define ALLOC_VAR(name, sz) \ bool *name = PyMem_Calloc(sz, sizeof(bool)); \ - if (name == NULL) return PyErr_NoMemory(); \ + if (name == NULL) { PyErr_NoMemory(); return false; } \ PyMem_Free(self->name); self->name = name; + self->ynum = ynum; self->xnum = xnum; ALLOC_VAR(changed_lines, self->ynum); ALLOC_VAR(changed_cells, self->xnum * self->ynum); ALLOC_VAR(lines_with_changed_cells, self->ynum); RESET_STATE_VARS(self); + return true; +} + +static PyObject* +resize(ChangeTracker *self, PyObject *args) { +#define resize_doc "Resize this change tracker must be called when the screen it is tracking for is resized" + unsigned int ynum=1, xnum=1; + if (!PyArg_ParseTuple(args, "|II", &ynum, &xnum)) return NULL; + if (!tracker_resize(self, ynum, xnum)) return NULL; Py_RETURN_NONE; } @@ -49,19 +50,10 @@ dealloc(ChangeTracker* self) { Py_TYPE(self)->tp_free((PyObject*)self); } -static inline void reset_inner(ChangeTracker *self) { - self->screen_changed = false; self->cursor_changed = false; self->dirty = false; - self->history_line_added_count = 0; - memset(self->changed_lines, 0, self->ynum * sizeof(bool)); - memset(self->changed_cells, 0, self->ynum * self->xnum * sizeof(bool)); - memset(self->lines_with_changed_cells, 0, self->ynum * sizeof(bool)); - RESET_STATE_VARS(self); -} - static PyObject* reset(ChangeTracker *self) { #define reset_doc "Reset all changes" - reset_inner(self); + tracker_reset(self); Py_RETURN_NONE; } @@ -156,7 +148,7 @@ update_cell_data(ChangeTracker *self, PyObject *args) { } PyObject *cursor_changed = self->cursor_changed ? Py_True : Py_False; - reset_inner(self); + tracker_reset(self); Py_INCREF(cursor_changed); return cursor_changed; } @@ -192,9 +184,8 @@ get_ranges(bool *line, unsigned int xnum) { return ans; } -static PyObject* -consolidate_changes(ChangeTracker *self) { -#define consolidate_changes_doc "" +PyObject* +tracker_consolidate_changes(ChangeTracker *self) { PyObject *ans = PyDict_New(); if (ans == NULL) return PyErr_NoMemory(); if (PyDict_SetItemString(ans, "screen", self->screen_changed ? Py_True : Py_False) != 0) { Py_CLEAR(ans); return NULL; } @@ -242,7 +233,7 @@ consolidate_changes(ChangeTracker *self) { if (PyDict_SetItemString(ans, "cells", t) != 0) { Py_CLEAR(t); Py_CLEAR(ans); return NULL; } Py_CLEAR(t); - reset_inner(self); + tracker_reset(self); return ans; } @@ -259,7 +250,7 @@ static PyMethodDef methods[] = { METHOD(update_cell_data, METH_VARARGS) METHOD(reset, METH_NOARGS) METHOD(cursor_changed, METH_NOARGS) - METHOD(consolidate_changes, METH_NOARGS) + {"consolidate_changes", (PyCFunction)tracker_consolidate_changes, METH_NOARGS, ""}, METHOD(line_added_to_history, METH_NOARGS) METHOD(update_screen, METH_NOARGS) METHOD(update_line_range, METH_VARARGS) @@ -283,6 +274,8 @@ PyTypeObject ChangeTracker_Type = { INIT_TYPE(ChangeTracker) // }}} -ChangeTracker* alloc_change_tracker() { - return (ChangeTracker*)new(&ChangeTracker_Type, NULL, NULL); +ChangeTracker* alloc_change_tracker(unsigned int ynum, unsigned int xnum) { + ChangeTracker *self = (ChangeTracker *)(&ChangeTracker_Type)->tp_alloc((&ChangeTracker_Type), 0); + if (!tracker_resize(self, ynum, xnum)) { Py_CLEAR(self); return NULL; } + return self; } diff --git a/kitty/tracker.h b/kitty/tracker.h index fc15458d2..276554ca2 100644 --- a/kitty/tracker.h +++ b/kitty/tracker.h @@ -37,3 +37,17 @@ static inline void tracker_update_cell_range(ChangeTracker *self, unsigned int l self->dirty = true; } } + +#define RESET_STATE_VARS(self) \ + self->screen_changed = false; self->cursor_changed = false; self->dirty = false; self->history_line_added_count = 0; + +static inline void tracker_reset(ChangeTracker *self) { + self->screen_changed = false; self->cursor_changed = false; self->dirty = false; + self->history_line_added_count = 0; + memset(self->changed_lines, 0, self->ynum * sizeof(bool)); + memset(self->changed_cells, 0, self->ynum * self->xnum * sizeof(bool)); + memset(self->lines_with_changed_cells, 0, self->ynum * sizeof(bool)); + RESET_STATE_VARS(self); +} + +PyObject* tracker_consolidate_changes(ChangeTracker *self); diff --git a/kitty_tests/__init__.py b/kitty_tests/__init__.py index 64ebda3d7..ebdc850bc 100644 --- a/kitty_tests/__init__.py +++ b/kitty_tests/__init__.py @@ -33,8 +33,8 @@ class BaseTest(TestCase): def create_screen(self, cols=5, lines=5, history_size=5): return S(history_size, columns=cols, lines=lines) - def create_screen2(self, cols=5, lines=5, history_size=5): - return Screen(history_size, None, lines, cols) + def create_screen2(self, cols=5, lines=5): + return Screen(None, lines, cols) def assertEqualAttributes(self, c1, c2): x1, y1, c1.x, c1.y = c1.x, c1.y, 0, 0 diff --git a/kitty_tests/screen.py b/kitty_tests/screen.py index 6b0467e36..9c550fb9e 100644 --- a/kitty_tests/screen.py +++ b/kitty_tests/screen.py @@ -4,14 +4,15 @@ from . import BaseTest -from kitty.screen import mo +from kitty.fast_data_types import DECAWM, IRM class TestScreen(BaseTest): def test_draw_fast(self): + s = self.create_screen2() + # Test in line-wrap, non-insert mode - s = self.create_screen() s.draw(b'a' * 5) self.ae(str(s.line(0)), 'a' * 5) self.ae(s.cursor.x, 5), self.ae(s.cursor.y, 0) @@ -30,7 +31,7 @@ class TestScreen(BaseTest): # Now test without line-wrap s.reset(), s.reset_dirty() - s.reset_mode(mo.DECAWM) + s.reset_mode(DECAWM) s.draw(b'0123456789') self.ae(str(s.line(0)), '01239') self.ae(s.cursor.x, 5), self.ae(s.cursor.y, 0) @@ -42,7 +43,7 @@ class TestScreen(BaseTest): # Now test in insert mode s.reset(), s.reset_dirty() - s.set_mode(mo.IRM) + s.set_mode(IRM) s.draw(b'12345' * 5) s.cursor_back(5) self.ae(s.cursor.x, 0), self.ae(s.cursor.y, 4) @@ -79,7 +80,7 @@ class TestScreen(BaseTest): # Now test without line-wrap s.reset(), s.reset_dirty() - s.reset_mode(mo.DECAWM) + s.reset_mode(DECAWM) s.draw('0\u030612345\u03066789\u0306'.encode('utf-8')) self.ae(str(s.line(0)), '0\u03061239\u0306') self.ae(s.cursor.x, 5), self.ae(s.cursor.y, 0) @@ -91,7 +92,7 @@ class TestScreen(BaseTest): # Now test in insert mode s.reset(), s.reset_dirty() - s.set_mode(mo.IRM) + s.set_mode(IRM) s.draw('1\u03062345'.encode('utf-8') * 5) s.cursor_back(5) self.ae(s.cursor.x, 0), self.ae(s.cursor.y, 4)