diff --git a/kitty/parser.c b/kitty/parser.c index d575f9738..79986e599 100644 --- a/kitty/parser.c +++ b/kitty/parser.c @@ -343,6 +343,7 @@ dispatch_osc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { // CSI mode {{{ #define CSI_SECONDARY \ case ';': \ + case ':': \ case '"': \ case '*': \ case '\'': \ @@ -382,6 +383,119 @@ repr_csi_params(unsigned int *params, unsigned int num_params) { return buf; } +static inline void +parse_sgr(Screen *screen, uint32_t *buf, unsigned int num, unsigned int *params, PyObject DUMP_UNUSED *dump_callback) { + enum State { START, NORMAL, MULTIPLE, COLOR, COLOR1, COLOR3 }; + enum State state = START; + unsigned int num_params, num_start, i; + +#define READ_PARAM { params[num_params++] = utoi(buf + num_start, i - num_start); } +#define SEND_SGR { REPORT_PARAMS(select_graphic_rendition, params, num_params); select_graphic_rendition(screen, params, num_params); state = START; num_params = 0; } + + for (i=0, num_start=0, num_params=0; i < num && num_params < MAX_PARAMS; i++) { + switch(buf[i]) { + IS_DIGIT + switch(state) { + case START: + num_start = i; + state = NORMAL; + num_params = 0; + break; + default: + break; + } + break; + case ';': + switch(state) { + case START: + params[num_params++] = 0; + SEND_SGR; + break; + case NORMAL: + READ_PARAM; + switch(params[0]) { + case 38: + case 48: + case 58: + state = COLOR; + num_start = i + 1; + break; + default: + SEND_SGR; + break; + } + break; + case MULTIPLE: + READ_PARAM; + SEND_SGR; + break; + case COLOR: + READ_PARAM; + switch(params[1]) { + case 2: + state = COLOR3; + break; + case 5: + state = COLOR1; + break; + default: + REPORT_ERROR("Invalid SGR color code with unknown color type: %u", params[1]); + return; + } + num_start = i + 1; + break; + case COLOR1: + READ_PARAM; + SEND_SGR; + break; + case COLOR3: + READ_PARAM; + if (num_params == 5) { SEND_SGR; } + else num_start = i + 1; + break; + } + break; + case ':': + switch(state) { + case START: + REPORT_ERROR("Invalid SGR code containing ':' at an invalid location: %u", i); + return; + case NORMAL: + READ_PARAM; + state = MULTIPLE; + num_start = i + 1; + break; + case MULTIPLE: + READ_PARAM; + num_start = i + 1; + break; + case COLOR: + case COLOR1: + case COLOR3: + REPORT_ERROR("Invalid SGR code containing disallowed character: %s", utf8(buf[i])); + return; + } + break; + default: + REPORT_ERROR("Invalid SGR code containing disallowed character: %s", utf8(buf[i])); + return; + } + } + switch(state) { + case COLOR1: + case COLOR3: + case NORMAL: + case MULTIPLE: + READ_PARAM; + SEND_SGR; + break; + default: + break; + } +#undef READ_PARAM +#undef SEND_SGR +} + static inline void dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { #define CALL_CSI_HANDLER1(name, defval) \ @@ -424,12 +538,6 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { } \ break; -#define CSI_HANDLER_MULTIPLE(name) \ - REPORT_PARAMS(name, params, num_params); \ - name(screen, params, num_params); \ - break; - - char start_modifier = 0, end_modifier = 0; uint32_t *buf = screen->parser_buf, code = screen->parser_buf[screen->parser_buf_pos]; unsigned int num = screen->parser_buf_pos, start, i, num_params=0, p1, p2; @@ -439,6 +547,10 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { start_modifier = (char)screen->parser_buf[0]; buf++; num--; } + if (code == SGR && !start_modifier) { + parse_sgr(screen, buf, num, params, dump_callback); + return; + } if (num > 0) { switch(buf[num-1]) { CSI_SECONDARY @@ -506,8 +618,6 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { SET_MODE(screen_set_mode); case RM: SET_MODE(screen_reset_mode); - case SGR: - CSI_HANDLER_MULTIPLE(select_graphic_rendition); case DSR: CALL_CSI_HANDLER1P(report_device_status, 0, '?'); case SC: diff --git a/kitty_tests/parser.py b/kitty_tests/parser.py index 98b00702d..de7ffcb58 100644 --- a/kitty_tests/parser.py +++ b/kitty_tests/parser.py @@ -115,19 +115,30 @@ class TestParser(BaseTest): pb('\033[?1000;1004h', ('screen_set_mode', 1000, 1), ('screen_set_mode', 1004, 1)) pb('\033[20;4;20l', ('screen_reset_mode', 20, 0), ('screen_reset_mode', 4, 0), ('screen_reset_mode', 20, 0)) s.reset() - pb('\033[1;3;4;7;9;34;44m', ('select_graphic_rendition', '1 3 4 7 9 34 44 ')) + + def sgr(params): + return (('select_graphic_rendition', '{} '.format(x)) for x in params.split()) + + pb('\033[1;3;4;7;9;34;44m', *sgr('1 3 4 7 9 34 44')) 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, 4 << 8 | 1) self.ae(s.cursor.bg, 4 << 8 | 1) - pb('\033[38;5;1;48;5;7m', ('select_graphic_rendition', '38 5 1 48 5 7 ')) + pb('\033[38;5;1;48;5;7m', ('select_graphic_rendition', '38 5 1 '), ('select_graphic_rendition', '48 5 7 ')) self.ae(s.cursor.fg, 1 << 8 | 1) self.ae(s.cursor.bg, 7 << 8 | 1) - pb('\033[38;2;1;2;3;48;2;7;8;9m', ('select_graphic_rendition', '38 2 1 2 3 48 2 7 8 9 ')) + pb('\033[38;2;1;2;3;48;2;7;8;9m', ('select_graphic_rendition', '38 2 1 2 3 '), ('select_graphic_rendition', '48 2 7 8 9 ')) self.ae(s.cursor.fg, 1 << 24 | 2 << 16 | 3 << 8 | 2) self.ae(s.cursor.bg, 7 << 24 | 8 << 16 | 9 << 8 | 2) - pb('\033[;2m', ('select_graphic_rendition', '0 2 ')) + pb('\033[0;2m', *sgr('0 2')) + pb('\033[;2m', *sgr('0 2')) + pb('\033[1;;2m', *sgr('1 0 2')) + pb('\033[38;5;1m', ('select_graphic_rendition', '38 5 1 ')) + pb('\033[38;2;1;2;3m', ('select_graphic_rendition', '38 2 1 2 3 ')) + pb('\033[1001:2:1:2:3m', ('select_graphic_rendition', '1001 2 1 2 3 ')) + pb('\033[38:2:1:2:3;48:5:9;58;5;7m', ( + 'select_graphic_rendition', '38 2 1 2 3 '), ('select_graphic_rendition', '48 5 9 '), ('select_graphic_rendition', '58 5 7 ')) c = s.callbacks pb('\033[5n', ('report_device_status', 5, 0)) self.ae(c.wtcbuf, b'\033[0n')