From f56da9392c51cbec393459ba9ed0fa978cb7c900 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 17 Nov 2016 08:34:55 +0530 Subject: [PATCH] Implement SGR --- kitty/cursor.c | 2 +- kitty/data-types.h | 7 +++ kitty/parser.c | 54 +++++++++++++++----- kitty/screen.c | 113 ++++++++++++++++++++++++++++++++++++++++-- kitty_tests/parser.py | 16 ++++++ 5 files changed, 175 insertions(+), 17 deletions(-) diff --git a/kitty/cursor.c b/kitty/cursor.c index 249b7b614..41dcee781 100644 --- a/kitty/cursor.c +++ b/kitty/cursor.c @@ -36,7 +36,7 @@ repr(Cursor *self) { ); } -static inline void cursor_reset_display_attrs(Cursor *self) { +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; } diff --git a/kitty/data-types.h b/kitty/data-types.h index a9582996e..447a66d03 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -275,6 +275,7 @@ uint16_t* translation_table(char); uint32_t decode_utf8(uint32_t*, uint32_t*, uint8_t byte); void cursor_reset(Cursor*); Cursor* cursor_copy(Cursor*); +void cursor_reset_display_attrs(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); @@ -314,6 +315,9 @@ void screen_reverse_index(Screen *self); void screen_index(Screen *self); void screen_reset(Screen *self); void screen_set_tab_stop(Screen *self); +void screen_clear_tab_stop(Screen *self, unsigned int how); +void screen_set_mode(Screen *self, unsigned int mode); +void screen_reset_mode(Screen *self, unsigned int mode); void screen_insert_characters(Screen *self, unsigned int count); void screen_cursor_up(Screen *self, unsigned int count/*=1*/, bool do_carriage_return/*=false*/, int move_direction/*=-1*/); void screen_cursor_to_column(Screen *self, unsigned int column); @@ -321,10 +325,13 @@ void screen_cursor_down(Screen *self, unsigned int count/*=1*/); void screen_cursor_forward(Screen *self, unsigned int count/*=1*/); void screen_cursor_down1(Screen *self, unsigned int count/*=1*/); void screen_cursor_up1(Screen *self, unsigned int count/*=1*/); +void screen_cursor_to_line(Screen *screen, unsigned int line); void screen_insert_lines(Screen *self, unsigned int count/*=1*/); void screen_delete_lines(Screen *self, unsigned int count/*=1*/); void screen_delete_characters(Screen *self, unsigned int count); void screen_erase_characters(Screen *self, unsigned int count); +void report_device_attributes(Screen *self, unsigned int UNUSED mode, bool UNUSED secondary); +void select_graphic_rendition(Screen *self, unsigned int *params, unsigned int count); #define DECLARE_CH_SCREEN_HANDLER(name) void screen_##name(Screen *screen, uint8_t ch); DECLARE_CH_SCREEN_HANDLER(bell) DECLARE_CH_SCREEN_HANDLER(backspace) diff --git a/kitty/parser.c b/kitty/parser.c index 6323050a7..c3b6ac853 100644 --- a/kitty/parser.c +++ b/kitty/parser.c @@ -216,7 +216,7 @@ HANDLER(esc) { // Parse CSI {{{ -#define MAX_PARAMS 8 +#define MAX_PARAMS 100 static inline unsigned int fill_params(Screen *screen, unsigned int *params, unsigned int expect) { unsigned int start_pos = 1, i = 1, pi = 0; @@ -256,20 +256,21 @@ HANDLER(csi) { case NUL: \ case DEL: \ break; // no-op + +#define END_DISPATCH SET_STATE(NORMAL_STATE); break; + #define CALL_CSI_HANDLER1(name, defval) \ p1 = fill_params(screen, params, 1) > 0 ? params[0] : defval; \ REPORT_COMMAND(name, p1); \ name(screen, p1); \ - SET_STATE(NORMAL_STATE); \ - break; + END_DISPATCH; -#define CALL_CSI_HANDLER1P(name, defval) \ +#define CALL_CSI_HANDLER1P(name, defval, qch) \ p1 = fill_params(screen, params, 1) > 0 ? params[0] : defval; \ - private = screen->parser_buf[0] == '?'; \ + private = screen->parser_buf[0] == qch; \ REPORT_COMMAND(name, p1, private); \ name(screen, p1, private); \ - SET_STATE(NORMAL_STATE); \ - break; + END_DISPATCH; #define CALL_CSI_HANDLER2(name, defval1, defval2) \ count = fill_params(screen, params, 2); \ @@ -277,8 +278,23 @@ HANDLER(csi) { p2 = count > 1 ? params[1] : defval2; \ REPORT_COMMAND(name, p1, p2); \ name(screen, p1, p2); \ - SET_STATE(NORMAL_STATE); \ - break; + END_DISPATCH; + +#define SET_MODE(func) \ + count = fill_params(screen, params, MAX_PARAMS); \ + p1 = screen->parser_buf[0] == '?' ? 5 : 0; \ + for (i = 0; i < count; i++) { \ + REPORT_COMMAND(func, params[i] << p1); \ + func(screen, params[i] << p1); \ + } \ + END_DISPATCH; + +#define CSI_HANDLER_MULTIPLE(name) \ + count = fill_params(screen, params, MAX_PARAMS); \ + REPORT_COMMAND(name, count); \ + name(screen, params, count); \ + END_DISPATCH; + #define DISPATCH_CSI \ case ICH: \ @@ -286,6 +302,7 @@ HANDLER(csi) { case CUU: \ CALL_CSI_HANDLER1(screen_cursor_up2, 1); \ case CUD: \ + case VPR: \ CALL_CSI_HANDLER1(screen_cursor_down, 1); \ case CUF: \ case HPR: \ @@ -298,12 +315,15 @@ HANDLER(csi) { CALL_CSI_HANDLER1(screen_cursor_up1, 1); \ case CHA: \ CALL_CSI_HANDLER1(screen_cursor_to_column, 1); \ + case VPA: \ + CALL_CSI_HANDLER1(screen_cursor_to_line, 1); \ case CUP: \ + case HVP: \ CALL_CSI_HANDLER2(screen_cursor_position, 1, 1); \ case ED: \ - CALL_CSI_HANDLER1P(screen_erase_in_display, 0); \ + CALL_CSI_HANDLER1P(screen_erase_in_display, 0, '?'); \ case EL: \ - CALL_CSI_HANDLER1P(screen_erase_in_line, 0); \ + CALL_CSI_HANDLER1P(screen_erase_in_line, 0, '?'); \ case IL: \ CALL_CSI_HANDLER1(screen_insert_lines, 1); \ case DL: \ @@ -312,9 +332,19 @@ HANDLER(csi) { CALL_CSI_HANDLER1(screen_delete_characters, 1); \ case ECH: \ CALL_CSI_HANDLER1(screen_erase_characters, 1); \ + case DA: \ + CALL_CSI_HANDLER1P(report_device_attributes, 0, '>'); \ + case TBC: \ + CALL_CSI_HANDLER1(screen_clear_tab_stop, 0); \ + case SM: \ + SET_MODE(screen_set_mode); \ + case RM: \ + SET_MODE(screen_reset_mode); \ + case SGR: \ + CSI_HANDLER_MULTIPLE(select_graphic_rendition); \ uint8_t ch = buf[(*pos)++]; - unsigned int params[MAX_PARAMS], p1, p2, count; + unsigned int params[MAX_PARAMS], p1, p2, count, i; bool private; switch(screen->parser_buf_pos) { case 0: // CSI starting diff --git a/kitty/screen.c b/kitty/screen.c index ad4bd7359..9b96ff990 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -221,6 +221,74 @@ void screen_alignment_display(Screen *self) { line_clear_text(self->linebuf->line, 0, self->linebuf->xnum, 'E'); } } + +void select_graphic_rendition(Screen *self, unsigned int *params, unsigned int count) { +#define SET_COLOR(which) \ + if (i < count) { \ + attr = params[i++];\ + switch(attr) { \ + case 5: \ + if (i < count) \ + self->cursor->which = (params[i++] & 0xFF) << 8 | 2; \ + break; \ + case 2: \ + if (i < count - 2) { \ + r = params[i++] & 0xFF; \ + g = params[i++] & 0xFF; \ + b = params[i++] & 0xFF; \ + self->cursor->which = r << 24 | g << 16 | b << 8 | 3; \ + }\ + break; \ + } \ + } \ + break; + + unsigned int i = 0, attr; + uint8_t r, g, b; + if (!count) { params[0] = 0; count = 1; } + while (i < count) { + attr = params[i++]; + switch(attr) { + case 0: + cursor_reset_display_attrs(self->cursor); break; + case 1: + self->cursor->bold = true; break; + case 3: + self->cursor->italic = true; break; + case 4: + self->cursor->decoration = 1; break; + case 7: + self->cursor->reverse = true; break; + case 9: + self->cursor->strikethrough = true; break; + case 22: + self->cursor->bold = false; break; + case 23: + self->cursor->italic = false; break; + case 24: + self->cursor->decoration = 0; break; + case 27: + self->cursor->reverse = false; break; + case 29: + self->cursor->strikethrough = false; break; +#pragma GCC diagnostic ignored "-Wpedantic" + case 30 ... 37: + case 39: + case 90 ... 97: + self->cursor->fg = (attr << 8) | 1; break; + case 40 ... 47: + case 49: + case 100 ... 107: +#pragma GCC diagnostic pop + self->cursor->bg = (attr << 8) | 1; break; + case 38: + SET_COLOR(fg); + case 48: + SET_COLOR(bg); + } + } +} + // }}} // Modes {{{ @@ -242,7 +310,7 @@ void screen_toggle_screen_buffer(Screen *self) { 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) { +static inline void set_mode_from_const(Screen *self, unsigned int mode, bool val) { switch(mode) { case LNM: self->modes.mLNM = val; break; @@ -265,7 +333,7 @@ static inline void set_mode_from_const(Screen *self, int mode, bool val) { } } -void screen_set_mode(Screen *self, int mode) { +void screen_set_mode(Screen *self, unsigned int mode) { if (mode == DECCOLM) { // When DECCOLM mode is set, the screen is erased and the cursor // moves to the home position. @@ -311,7 +379,7 @@ enable_focus_tracking(Screen *self) { return ans; } -void screen_reset_mode(Screen *self, int mode) { +void screen_reset_mode(Screen *self, unsigned int mode) { if (mode == DECCOLM) { // When DECCOLM mode is set, the screen is erased and the cursor // moves to the home position. @@ -361,6 +429,17 @@ void screen_tab(Screen *self, uint8_t UNUSED ch) { } } +void screen_clear_tab_stop(Screen *self, unsigned int how) { + switch(how) { + case 0: + if ((unsigned int)self->cursor->x < self->columns) self->tabstops[self->cursor->x] = false; + break; + case 3: + break; + for (unsigned int i = 0; i < self->columns; i++) self->tabstops[i] = false; + } +} + void screen_set_tab_stop(Screen *self) { if ((unsigned int)self->cursor->x < self->columns && self->cursor->x >= 0) self->tabstops[self->cursor->x] = true; @@ -506,6 +585,16 @@ void screen_cursor_position(Screen *self, unsigned int line, unsigned int column if (x != self->cursor->x || y != self->cursor->y) tracker_cursor_changed(self->change_tracker); } +void screen_cursor_to_line(Screen *self, unsigned int line) { + unsigned int y = MAX(line, 1) - 1; + y += self->margin_top; + if (y != (unsigned int)self->cursor->y) { + self->cursor->y = y; + screen_ensure_bounds(self, false); // TODO: should we also restrict the cursor to the scrolling region? + tracker_cursor_changed(self->change_tracker); + } +} + // }}} // Editing {{{ @@ -645,6 +734,23 @@ void screen_erase_characters(Screen *self, unsigned int count) { 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); } + +// }}} + +// Device control {{{ + +static inline void write_to_child(Screen *self, const char *data, unsigned int sz) { + if (sz) PyObject_CallMethod(self->callbacks, "write_to_child", "y#", data, sz); + else PyObject_CallMethod(self->callbacks, "write_to_child", "y", data); + if (PyErr_Occurred()) PyErr_Print(); + PyErr_Clear(); +} + +void report_device_attributes(Screen *self, unsigned int UNUSED mode, bool UNUSED secondary) { + // Do the same as libvte, which gives the below response regardless of mode and secondary + write_to_child(self, "\x1b[?62c", 0); // Corresponds to VT-220 +} + // }}} // Python interface {{{ @@ -829,4 +935,3 @@ PyTypeObject Screen_Type = { INIT_TYPE(Screen) // }}} - diff --git a/kitty_tests/parser.py b/kitty_tests/parser.py index 619441908..d1ccaf7c9 100644 --- a/kitty_tests/parser.py +++ b/kitty_tests/parser.py @@ -76,4 +76,20 @@ class TestScreen(BaseTest): pb('\033[J', ('screen_erase_in_display', 0, 0)) pb('\033[?J', ('screen_erase_in_display', 0, 1)) pb('\033[?2J', ('screen_erase_in_display', 2, 1)) + pb('\033[h') + pb('\033[20;4h', ('screen_set_mode', 20), ('screen_set_mode', 4)) + pb('\033[?20;5h', ('screen_set_mode', 20 << 5), ('screen_set_mode', 5 << 5)) + pb('\033[20;4;145l', ('screen_reset_mode', 20), ('screen_reset_mode', 4), ('screen_reset_mode', 145)) s.reset() + pb('\033[1;3;4;7;9;34;44m', ('select_graphic_rendition', 7)) + for attr in 'bold italic reverse strikethrough'.split(): + self.assertTrue(getattr(s.cursor, attr)) + self.ae(s.cursor.decoration, 1) + self.ae(s.cursor.fg, 34 << 8 | 1) + self.ae(s.cursor.bg, 44 << 8 | 1) + pb('\033[38;5;1;48;5;7m', ('select_graphic_rendition', 6)) + self.ae(s.cursor.fg, 1 << 8 | 2) + self.ae(s.cursor.bg, 7 << 8 | 2) + pb('\033[38;2;1;2;3;48;2;7;8;9m', ('select_graphic_rendition', 10)) + self.ae(s.cursor.fg, 1 << 24 | 2 << 16 | 3 << 8 | 3) + self.ae(s.cursor.bg, 7 << 24 | 8 << 16 | 9 << 8 | 3)