From f14e7037e205874e8ca7247acbd4e84a4eb1335a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 23 Nov 2016 15:33:08 +0530 Subject: [PATCH] Start work on dumping non-UTF-8 charset support --- kitty/control-codes.h | 33 +- kitty/data-types.h | 16 +- kitty/parser.c | 686 ++++++++++++------------------------------ kitty/screen.c | 128 ++------ kitty_tests/parser.py | 2 +- 5 files changed, 240 insertions(+), 625 deletions(-) diff --git a/kitty/control-codes.h b/kitty/control-codes.h index d033b6d60..b3ba8d767 100644 --- a/kitty/control-codes.h +++ b/kitty/control-codes.h @@ -55,13 +55,13 @@ // *Delete*: Is ignored. #define DEL 0x7f -// *Control sequence introducer*: An equivalent for ``ESC [``. +#define IND 0x84 +#define NEL 0x85 +#define HTS 0x88 +#define RI 0x8d +#define DCS 0x90 #define CSI 0x9b - -// *String terminator*. #define ST 0x9c - -// *Operating system command*. #define OSC 0x9d // Sharp control codes @@ -73,38 +73,43 @@ // Esc control codes // ------------------ +#define ESC_DCS 'P' +#define ESC_OSC ']' +#define ESC_CSI '[' +#define ESC_ST '\\' + // *Reset*. -#define RIS 'c' +#define ESC_RIS 'c' // *Index*: Move cursor down one line in same column. If the cursor is // at the bottom margin, the screen performs a scroll-up. -#define IND 'D' +#define ESC_IND 'D' // *Next line*: Same as LF. -#define NEL 'E' +#define ESC_NEL 'E' // Tabulation set: Set a horizontal tab stop at cursor position. -#define HTS 'H' +#define ESC_HTS 'H' // *Reverse index*: Move cursor up one line in same column. If the // cursor is at the top margin, the screen performs a scroll-down. -#define RI 'M' +#define ESC_RI 'M' // Save cursor: Save cursor position, character attribute (graphic // rendition), character set, and origin mode selection (see // :data:`DECRC`). -#define DECSC '7' +#define ESC_DECSC '7' // *Restore cursor*: Restore previously saved cursor position, character // attribute (graphic rendition), character set, and origin mode // selection. If none were saved, move cursor to home position. -#define DECRC '8' +#define ESC_DECRC '8' // Set normal keypad mode -#define DECPNM '>' +#define ESC_DECPNM '>' // Set alternate keypad mode -#define DECPAM '=' +#define ESC_DECPAM '=' // ECMA-48 CSI sequences. // --------------------- diff --git a/kitty/data-types.h b/kitty/data-types.h index d740bd105..6d8c2d4e4 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -227,8 +227,6 @@ PyTypeObject ScreenModes_Type; #define SAVEPOINTS_SZ 256 typedef struct { - unsigned int current_charset; - uint16_t *g0_charset, *g1_charset; uint32_t utf8_state; Cursor cursor; bool mDECOM; @@ -243,16 +241,14 @@ typedef struct { } SavepointBuffer; -#define PARSER_BUF_SZ 8192 +#define PARSER_BUF_SZ (8 * 1024) #define READ_BUF_SZ (1024*1024) typedef struct { PyObject_HEAD unsigned int columns, lines, margin_top, margin_bottom; - unsigned int current_charset; uint32_t utf8_state; - uint16_t *g0_charset, *g1_charset; Cursor *cursor; SavepointBuffer main_savepoints, alt_savepoints; PyObject *callbacks; @@ -262,7 +258,7 @@ typedef struct { ChangeTracker *change_tracker; ScreenModes modes; - uint8_t parser_buf[PARSER_BUF_SZ]; + uint32_t parser_buf[PARSER_BUF_SZ]; unsigned int parser_state, parser_text_start, parser_buf_pos; bool parser_has_pending_text; uint8_t read_buf[READ_BUF_SZ]; @@ -334,14 +330,12 @@ void screen_cursor_position(Screen*, unsigned int, unsigned int); void screen_cursor_back(Screen *self, unsigned int count/*=1*/, int move_direction/*=-1*/); void screen_erase_in_line(Screen *, unsigned int, bool); void screen_erase_in_display(Screen *, unsigned int, bool); -void screen_draw(Screen *screen, uint8_t *buf, unsigned int buflen); +void screen_draw(Screen *screen, uint32_t codepoint); void screen_ensure_bounds(Screen *self, bool use_margins); void 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); -void screen_define_charset(Screen *self, uint8_t code, uint8_t mode); -void screen_select_other_charset(Screen *self, uint8_t code, uint8_t mode); void screen_alignment_display(Screen *self); void screen_reverse_index(Screen *self); void screen_index(Screen *self); @@ -370,11 +364,9 @@ void set_dynamic_color(Screen *self, unsigned int code, const char *buf, unsigne 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); void report_device_status(Screen *self, unsigned int which, bool UNUSED); -#define DECLARE_CH_SCREEN_HANDLER(name) void screen_##name(Screen *screen, uint8_t ch); +#define DECLARE_CH_SCREEN_HANDLER(name) void screen_##name(Screen *screen); DECLARE_CH_SCREEN_HANDLER(bell) DECLARE_CH_SCREEN_HANDLER(backspace) DECLARE_CH_SCREEN_HANDLER(tab) DECLARE_CH_SCREEN_HANDLER(linefeed) DECLARE_CH_SCREEN_HANDLER(carriage_return) -DECLARE_CH_SCREEN_HANDLER(shift_out) -DECLARE_CH_SCREEN_HANDLER(shift_in) diff --git a/kitty/parser.c b/kitty/parser.c index 044463da5..fdccf7819 100644 --- a/kitty/parser.c +++ b/kitty/parser.c @@ -1,35 +1,13 @@ /* - * parser.c * Copyright (C) 2016 Kovid Goyal * * Distributed under terms of the GPL3 license. */ -#include -#include -#include -#include #include "data-types.h" #include "control-codes.h" -#define NORMAL_STATE 0 -#define ESC_STATE 1 -#define CSI_STATE 2 -#define OSC_STATE 3 -#define DCS_STATE 4 - -#define IS_DIGIT \ - case '0': \ - case '1': \ - case '2': \ - case '3': \ - case '4': \ - case '5': \ - case '6': \ - case '7': \ - case '8': \ - case '9': - +// Macros {{{ #ifdef DUMP_COMMANDS static void _report_error(PyObject *dump_callback, const char *fmt, ...) { va_list argptr; @@ -42,6 +20,8 @@ static void _report_error(PyObject *dump_callback, const char *fmt, ...) { } } +#define DUMP_UNUSED + #define REPORT_ERROR(...) _report_error(dump_callback, __VA_ARGS__); #define REPORT_COMMAND1(name) \ @@ -56,527 +36,244 @@ static void _report_error(PyObject *dump_callback, const char *fmt, ...) { #define GET_MACRO(_1,_2,_3,NAME,...) NAME #define REPORT_COMMAND(...) GET_MACRO(__VA_ARGS__, REPORT_COMMAND3, REPORT_COMMAND2, REPORT_COMMAND1, SENTINEL)(__VA_ARGS__) -#define REPORT_DRAW(start, sz) \ - Py_XDECREF(PyObject_CallFunction(dump_callback, "sy#", "draw", start, sz)); PyErr_Clear(); - -#define REPORT_DCS \ - Py_XDECREF(PyObject_CallFunction(dump_callback, "sy#", "dcs", screen->parser_buf, screen->parser_buf_pos)); PyErr_Clear(); - -#define HANDLER(name) \ - static inline void read_##name(Screen *screen, uint8_t UNUSED *buf, unsigned int UNUSED buflen, unsigned int UNUSED *pos, PyObject UNUSED *dump_callback) +#define REPORT_DRAW(ch) \ + Py_XDECREF(PyObject_CallFunction(dump_callback, "sC", "draw", ch)); PyErr_Clear(); #else +#define DUMP_UNUSED UNUSED + #define REPORT_ERROR(...) fprintf(stderr, "[PARSE ERROR] "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); #define REPORT_COMMAND(...) -#define REPORT_DRAW(...) -#define REPORT_DCS - -#define HANDLER(name) \ - static inline void read_##name(Screen *screen, uint8_t UNUSED *buf, unsigned int UNUSED buflen, unsigned int UNUSED *pos) +#define REPORT_DRAW(ch) #endif - #define SET_STATE(state) screen->parser_state = state; screen->parser_buf_pos = 0; - -// Parse text {{{ - - -HANDLER(text) { - uint8_t ch; - unsigned int sz; - - while(*pos < buflen) { - ch = buf[(*pos)++]; -#define DRAW_TEXT(limit) \ - if (screen->parser_has_pending_text) { \ - screen->parser_has_pending_text = false; \ - sz = (limit) > screen->parser_text_start ? (limit) - screen->parser_text_start : 0; \ - REPORT_DRAW(buf + screen->parser_text_start, sz); \ - screen_draw(screen, buf + screen->parser_text_start, sz); \ - screen->parser_text_start = 0; \ - } - -#define CALL_SCREEN_HANDLER(name) \ - DRAW_TEXT((*pos) - 1); REPORT_COMMAND(name, ch); \ - name(screen, ch); break; - - switch(ch) { - case BEL: - CALL_SCREEN_HANDLER(screen_bell); - case BS: - CALL_SCREEN_HANDLER(screen_backspace); - case HT: - CALL_SCREEN_HANDLER(screen_tab); - case LF: - case VT: - case FF: - CALL_SCREEN_HANDLER(screen_linefeed); - case CR: - CALL_SCREEN_HANDLER(screen_carriage_return); - case SO: - CALL_SCREEN_HANDLER(screen_shift_out); - case SI: - CALL_SCREEN_HANDLER(screen_shift_in); - case ESC: - DRAW_TEXT((*pos)-1); SET_STATE(ESC_STATE); return; - case CSI: - DRAW_TEXT((*pos)-1); SET_STATE(CSI_STATE); return; - case OSC: - DRAW_TEXT((*pos)-1); SET_STATE(OSC_STATE); return; - case NUL: - case DEL: - break; // no-op - default: - if (!screen->parser_has_pending_text) { - screen->parser_has_pending_text = true; - screen->parser_text_start = (*pos) - 1; - } - } - } - DRAW_TEXT(*pos); -} -#undef DRAW_TEXT // }}} -// Parse ESC {{{ - -static inline void screen_linefeed2(Screen *screen) { screen_linefeed(screen, '\n'); } - -static inline void escape_dispatch(Screen *screen, uint8_t ch, PyObject UNUSED *dump_callback) { -#define CALL_ED(name) REPORT_COMMAND(name); name(screen); break; - switch (ch) { - case RIS: - CALL_ED(screen_reset); +// Normal mode {{{ +static inline void +handle_normal_mode_char(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback) { +#define CALL_SCREEN_HANDLER(name) REPORT_COMMAND(#name, ch); name(screen); break; + switch(ch) { + case BEL: + CALL_SCREEN_HANDLER(screen_bell); + case BS: + CALL_SCREEN_HANDLER(screen_backspace); + case HT: + CALL_SCREEN_HANDLER(screen_tab); + case LF: + case VT: + case FF: + CALL_SCREEN_HANDLER(screen_linefeed); + case CR: + CALL_SCREEN_HANDLER(screen_carriage_return); + case SO: + REPORT_ERROR("Unhandled charset change command (SO), ignoring"); break; + case SI: + REPORT_ERROR("Unhandled charset change command (SI), ignoring"); break; case IND: - CALL_ED(screen_index); - case NEL: - CALL_ED(screen_linefeed2); + CALL_SCREEN_HANDLER(screen_index); case RI: - CALL_ED(screen_reverse_index); + CALL_SCREEN_HANDLER(screen_reverse_index); + case NEL: + CALL_SCREEN_HANDLER(screen_linefeed); case HTS: - CALL_ED(screen_set_tab_stop); - case DECSC: - CALL_ED(screen_save_cursor); - case DECRC: - CALL_ED(screen_restore_cursor); - case DECPNM: - CALL_ED(screen_normal_keypad_mode); - case DECPAM: - CALL_ED(screen_alternate_keypad_mode); + CALL_SCREEN_HANDLER(screen_set_tab_stop); + case ESC: + case CSI: + case OSC: + case DCS: + SET_STATE(ch); break; + case NUL: + case DEL: + break; // no-op default: - REPORT_ERROR("%s%d", "Unknown char in escape_dispatch: ", ch); + REPORT_DRAW(ch); + screen_draw(screen, ch); + break; + } +#undef CALL_SCREEN_HANDLER +} // }}} + +// Esc mode {{{ +static inline void +handle_esc_mode_char(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback) { +#define CALL_ED(name) REPORT_COMMAND(name, ch); name(screen); SET_STATE(0); break; + switch(screen->parser_buf_pos) { + case 0: + switch (ch) { + case ESC_DCS: + SET_STATE(DCS); break; + case ESC_OSC: + SET_STATE(OSC); break; + case ESC_CSI: + SET_STATE(CSI); break; + case ESC_RIS: + CALL_ED(screen_reset); + case ESC_IND: + CALL_ED(screen_index); + case ESC_NEL: + CALL_ED(screen_linefeed); + case ESC_RI: + CALL_ED(screen_reverse_index); + case ESC_HTS: + CALL_ED(screen_set_tab_stop); + case ESC_DECSC: + CALL_ED(screen_save_cursor); + case ESC_DECRC: + CALL_ED(screen_restore_cursor); + case ESC_DECPNM: + CALL_ED(screen_normal_keypad_mode); + case ESC_DECPAM: + CALL_ED(screen_alternate_keypad_mode); + case '%': + case '(': + case ')': + case '*': + case '+': + case '-': + case '.': + case '/': + case ' ': + screen->parser_buf[screen->parser_buf_pos++] = ch; + break; + default: + REPORT_ERROR("%s0x%x", "Unknown char after ESC: ", ch); + SET_STATE(0); break; + } + break; + default: + if (screen->parser_buf[0] == '%' && ch == 'G') { + // switch to utf-8, since we are always in utf-8, ignore. + } else { + REPORT_ERROR("Unhandled charset related escape code: 0x%x 0x%x", screen->parser_buf[0], screen->parser_buf[1]); + } + SET_STATE(0); break; } #undef CALL_ED -} +} // }}} -static inline void sharp_dispatch(Screen *screen, uint8_t ch, PyObject UNUSED *dump_callback) { - switch(ch) { - case DECALN: - REPORT_COMMAND(screen_alignment_display); - screen_alignment_display(screen); - break; - default: - REPORT_ERROR("%s%d", "Unknown char in sharp_dispatch: ", ch); - } +// OSC mode {{{ +static inline void +dispatch_osc(Screen *screen) { + screen->parser_buf_pos++; } - -HANDLER(esc) { -#define ESC_DISPATCH(which, extra) REPORT_COMMAND(which, ch, extra); which(screen, ch, extra); SET_STATE(NORMAL_STATE); return; -#ifdef DUMP_COMMANDS -#define ESC_DELEGATE(which) which(screen, ch, dump_callback); SET_STATE(NORMAL_STATE); return; -#else -#define ESC_DELEGATE(which) which(screen, ch, NULL); SET_STATE(NORMAL_STATE); return; -#endif - uint8_t ch = buf[(*pos)++]; - switch(screen->parser_buf_pos) { - case 0: - switch(ch) { - case '[': - SET_STATE(CSI_STATE); return; - case ']': - SET_STATE(OSC_STATE); return; - case 'P': - SET_STATE(DCS_STATE); return; - case '#': - case '%': - case '(': - case ')': - screen->parser_buf[0] = ch; screen->parser_buf_pos++; return; - default: - ESC_DELEGATE(escape_dispatch); - } - break; - case 1: - switch(screen->parser_buf[0]) { - case '#': - ESC_DELEGATE(sharp_dispatch); - case '%': - ESC_DISPATCH(screen_select_other_charset, 0); - case '(': - case ')': - ESC_DISPATCH(screen_define_charset, screen->parser_buf[0]); - } - break; - } -#undef ESC_DISPATCH -#undef ESC_DELEGATE -} - // }}} -// Parse CSI {{{ - -#define MAX_PARAMS 100 - -static inline unsigned int fill_params(Screen *screen, unsigned int *params, unsigned int expect) { - unsigned int start_pos = 2, i = 2, pi = 0; - uint8_t ch = 1; - screen->parser_buf[screen->parser_buf_pos] = 0; - - while (pi < MIN(MAX_PARAMS, expect) && i < PARSER_BUF_SZ - 1 && ch != 0) { - ch = screen->parser_buf[i++]; - if (ch == 0 || ch == ';') { - if (start_pos < i - 1) { - params[pi++] = atoi((const char *)screen->parser_buf + start_pos); - } - start_pos = i; - } - } - return pi; +// DCS mode {{{ +static inline void +dispatch_dcs(Screen *screen) { + screen->parser_buf_pos++; } - -static inline void screen_cursor_up2(Screen *s, unsigned int count) { screen_cursor_up(s, count, false, -1); } -static inline void screen_cursor_back1(Screen *s, unsigned int count) { screen_cursor_back(s, count, -1); } - -HANDLER(csi) { -#define CALL_BASIC_HANDLER(name) REPORT_COMMAND(screen, ch); name(screen, ch); break; -#define HANDLE_BASIC_CH \ - case BEL: \ - CALL_BASIC_HANDLER(screen_bell); \ - case BS: \ - CALL_BASIC_HANDLER(screen_backspace); \ - case HT: \ - CALL_BASIC_HANDLER(screen_tab); \ - case LF: \ - case VT: \ - case FF: \ - CALL_BASIC_HANDLER(screen_linefeed); \ - case CR: \ - CALL_BASIC_HANDLER(screen_carriage_return); \ - 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); \ - END_DISPATCH; - -#define CALL_CSI_HANDLER1P(name, defval, qch) \ - p1 = fill_params(screen, params, 1) > 0 ? params[0] : defval; \ - private = screen->parser_buf[0] == qch; \ - REPORT_COMMAND(name, p1, private); \ - name(screen, p1, private); \ - END_DISPATCH; - -#define CALL_CSI_HANDLER1M(name, defval) \ - p1 = fill_params(screen, params, 1) > 0 ? params[0] : defval; \ - REPORT_COMMAND(name, p1, screen->parser_buf[1]); \ - name(screen, p1, screen->parser_buf[1]); \ - END_DISPATCH; - -#define CALL_CSI_HANDLER2(name, defval1, defval2) \ - count = fill_params(screen, params, 2); \ - p1 = count > 0 ? params[0] : defval1; \ - p2 = count > 1 ? params[1] : defval2; \ - REPORT_COMMAND(name, p1, p2); \ - name(screen, p1, p2); \ - 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: \ - CALL_CSI_HANDLER1(screen_insert_characters, 1); \ - case CUU: \ - CALL_CSI_HANDLER1(screen_cursor_up2, 1); \ - case CUD: \ - case VPR: \ - CALL_CSI_HANDLER1(screen_cursor_down, 1); \ - case CUF: \ - case HPR: \ - CALL_CSI_HANDLER1(screen_cursor_forward, 1); \ - case CUB: \ - CALL_CSI_HANDLER1(screen_cursor_back1, 1); \ - case CNL: \ - CALL_CSI_HANDLER1(screen_cursor_down1, 1); \ - case CPL: \ - CALL_CSI_HANDLER1(screen_cursor_up1, 1); \ - case CHA: \ - case HPA: \ - 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, '?'); \ - case EL: \ - CALL_CSI_HANDLER1P(screen_erase_in_line, 0, '?'); \ - case IL: \ - CALL_CSI_HANDLER1(screen_insert_lines, 1); \ - case DL: \ - CALL_CSI_HANDLER1(screen_delete_lines, 1); \ - case DCH: \ - 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); \ - case DSR: \ - CALL_CSI_HANDLER1P(report_device_status, 0, '?'); \ - case DECSTBM: \ - CALL_CSI_HANDLER2(screen_set_margins, 0, 0); \ - case DECSCUSR: \ - CALL_CSI_HANDLER1M(screen_set_cursor, 1); \ - - uint8_t ch = buf[(*pos)++]; - unsigned int params[MAX_PARAMS], p1, p2, count, i; - bool private; - switch(screen->parser_buf_pos) { - case 0: // CSI starting - screen->parser_buf[0] = 0; - screen->parser_buf[1] = 0; - screen->parser_buf[2] = 0; - switch(ch) { - IS_DIGIT - screen->parser_buf_pos = 3; - screen->parser_buf[2] = ch; - break; - case '?': - case '>': - case '!': - screen->parser_buf[0] = ch; screen->parser_buf_pos = 1; - break; - HANDLE_BASIC_CH - DISPATCH_CSI - default: - REPORT_ERROR("Invalid first character for CSI: 0x%x", ch); - SET_STATE(NORMAL_STATE); - break; - } - break; - case 1: - screen->parser_buf_pos = 2; // we start params at 2 - default: // CSI started - switch(ch) { - IS_DIGIT - case ';': - if (screen->parser_buf_pos >= PARSER_BUF_SZ - 1) { - REPORT_ERROR("%s", "CSI sequence too long, ignoring."); - SET_STATE(NORMAL_STATE); - } else screen->parser_buf[screen->parser_buf_pos++] = ch; - break; - case ' ': - case '"': - screen->parser_buf[1] = ch; - break; - HANDLE_BASIC_CH - DISPATCH_CSI - default: - REPORT_ERROR("Invalid character for CSI: 0x%x", ch); - SET_STATE(NORMAL_STATE); - break; - } - break; - } -#undef CALL_BASIC_HANDLER -#undef HANDLE_BASIC_CH -#undef CALL_CSI_HANDLER1 -} -#undef MAX_PARAMS // }}} -// Parse OSC {{{ +// Parse loop {{{ -static inline void handle_osc(Screen *screen, PyObject UNUSED *dump_callback) { - unsigned int code = 0; - unsigned int start = screen->parser_buf[0] ? screen->parser_buf[0] : 2; - unsigned int sz = screen->parser_buf_pos > start ? screen->parser_buf_pos - start : 0; - screen->parser_buf[screen->parser_buf_pos] = 0; - if (screen->parser_buf[0] && screen->parser_buf[1]) code = (unsigned int)atoi((const char*)screen->parser_buf + 2); -#define DISPATCH_OSC(name) \ - REPORT_COMMAND(name, sz); \ - name(screen, (const char*)(screen->parser_buf + start), sz); - - switch(code) { - case 0: - DISPATCH_OSC(set_title); - DISPATCH_OSC(set_icon); - break; - case 1: - DISPATCH_OSC(set_icon); - break; - case 2: - DISPATCH_OSC(set_title); - break; - case 10: - case 11: - case 110: - case 111: - REPORT_COMMAND(set_dynamic_color, code, sz); - set_dynamic_color(screen, code, (const char*)(screen->parser_buf + start), sz); - break; - default: - REPORT_ERROR("Unknown OSC code: %u", code); - } -#undef DISPATCH_OSC -} - - -HANDLER(osc) { -#ifdef DUMP_COMMANDS -#define HANDLE_OSC handle_osc(screen, dump_callback); -#else -#define HANDLE_OSC handle_osc(screen, NULL); -#endif - uint8_t ch = buf[(*pos)++]; - if (screen->parser_buf_pos == 0) { - screen->parser_buf[0] = 0; - screen->parser_buf[1] = 1; - screen->parser_buf_pos = 2; - } +static inline bool +accumulate_osc(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback) { switch(ch) { case ST: + return true; + case ESC_ST: + if (screen->parser_buf_pos > 0 && screen->parser_buf[screen->parser_buf_pos - 1] == ESC) { + screen->parser_buf_pos--; + return true; + } case BEL: - if(!screen->parser_buf[0] && screen->parser_buf[1]) { - // Only a numeric component - screen->parser_buf[0] = screen->parser_buf_pos; - } - HANDLE_OSC; - SET_STATE(NORMAL_STATE); + return true; + case NUL: + case DEL: break; - case 0: - break; // ignore null bytes - case ';': - if (!screen->parser_buf[0] && screen->parser_buf_pos < 10) { - // Initial numeric parameter found - screen->parser_buf[0] = screen->parser_buf_pos; - break; - } default: - if (!screen->parser_buf[0] && (ch < '0' || ch > '9')) { - screen->parser_buf[1] = 0; // No initial numeric parameter - } if (screen->parser_buf_pos >= PARSER_BUF_SZ - 1) { - REPORT_ERROR("OSC control sequence too long, truncating"); - HANDLE_OSC; - SET_STATE(NORMAL_STATE); - break; + REPORT_ERROR("OSC sequence too long, truncating."); + return true; } screen->parser_buf[screen->parser_buf_pos++] = ch; + break; } -} -// }}} - -// Parse DCS {{{ - -static void handle_dcs(Screen *screen, PyObject UNUSED *dump_callback) { - screen->parser_buf_pos = 0; - return; + return false; } -HANDLER(dcs) { - // http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Device-Control-functions -#ifndef DUMP_COMMANDS - PyObject *dump_callback = NULL; -#endif -#define DISPATCH_DCS \ - SET_STATE(NORMAL_STATE); \ - if (screen->parser_buf_pos == 0) { REPORT_ERROR("Empty DCS sequence, ignoring."); return; } \ - REPORT_DCS; \ - handle_dcs(screen, dump_callback); - - uint8_t ch = buf[(*pos)++]; - switch (ch) { +static inline bool +accumulate_dcs(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback) { + switch(ch) { case ST: - DISPATCH_DCS; + return true; + case NUL: + case DEL: + break; + case ESC: +#pragma GCC diagnostic ignored "-Wpedantic" + case 32 ... 126: +#pragma GCC diagnostic pop + if (screen->parser_buf_pos > 0 && screen->parser_buf[screen->parser_buf_pos-1] == ESC) { + if (ch == '\\') { screen->parser_buf_pos--; return true; } + REPORT_ERROR("DCS sequence contained non-printable character: 0x%x ignoring the sequence", ESC); + SET_STATE(ESC); return false; + } + if (screen->parser_buf_pos >= PARSER_BUF_SZ - 1) { + REPORT_ERROR("DCS sequence too long, truncating."); + return true; + } + screen->parser_buf[screen->parser_buf_pos++] = ch; break; default: - if (screen->parser_buf_pos >= PARSER_BUF_SZ - 1) { - DISPATCH_DCS; - } else screen->parser_buf[screen->parser_buf_pos++] = ch; - break; + REPORT_ERROR("DCS sequence contained non-printable character: 0x%x ignoring the sequence", ch); } + return false; +} + +static inline void +_parse_bytes(Screen *screen, uint8_t *buf, Py_ssize_t len, PyObject DUMP_UNUSED *dump_callback) { +#define HANDLE(name) handle_##name(screen, codepoint, dump_callback); break + uint32_t prev = screen->utf8_state, codepoint = 0; + for (unsigned int i = 0; i < len; i++, prev = screen->utf8_state) { + switch (decode_utf8(&screen->utf8_state, &codepoint, buf[i])) { + case UTF8_ACCEPT: + switch(screen->parser_state) { + case ESC: + HANDLE(esc_mode_char); + /* case CSI_STATE: */ + /* CALL_HANDLER(csi); */ + case OSC: + if (accumulate_osc(screen, codepoint, dump_callback)) { dispatch_osc(screen); SET_STATE(0); } + break; + case DCS: + if (accumulate_dcs(screen, codepoint, dump_callback)) { dispatch_dcs(screen); SET_STATE(0); } + if (screen->parser_state == ESC) { HANDLE(esc_mode_char); } + break; + default: + HANDLE(normal_mode_char); + } + break; + case UTF8_REJECT: + screen->utf8_state = UTF8_ACCEPT; + if (prev != UTF8_ACCEPT) i--; + break; + } + } +#undef HANDLE } // }}} -static inline void _parse_bytes(Screen *screen, uint8_t *buf, Py_ssize_t len, PyObject UNUSED *dump_callback) { - unsigned int i = 0; +// Boilerplate {{{ #ifdef DUMP_COMMANDS -#define CALL_HANDLER(name) read_##name(screen, buf, len, &i, dump_callback); break; +#define FNAME(x) x##_dump #else -#define CALL_HANDLER(name) read_##name(screen, buf, len, &i); break; +#define FNAME(x) x #endif - /* PyObject_Print(Py_BuildValue("y#", buf, len), stdout, 0); */ - - while (i < len) { - switch(screen->parser_state) { - case ESC_STATE: - CALL_HANDLER(esc); - case CSI_STATE: - CALL_HANDLER(csi); - case OSC_STATE: - CALL_HANDLER(osc); - case DCS_STATE: - CALL_HANDLER(dcs); - default: - CALL_HANDLER(text); - } - } -} PyObject* -#ifdef DUMP_COMMANDS -parse_bytes_dump(PyObject UNUSED *self, PyObject *args) { -#else -parse_bytes(PyObject UNUSED *self, PyObject *args) { -#endif +FNAME(parse_bytes)(PyObject UNUSED *self, PyObject *args) { PyObject *dump_callback = NULL; Py_buffer pybuf; Screen *screen; #ifdef DUMP_COMMANDS if (!PyArg_ParseTuple(args, "OO!y*", &dump_callback, &Screen_Type, &screen, &pybuf)) return NULL; - if (!PyCallable_Check(dump_callback)) { PyErr_SetString(PyExc_TypeError, "The dump callback must be a callable object"); return NULL; } #else if (!PyArg_ParseTuple(args, "O!y*", &Screen_Type, &screen, &pybuf)) return NULL; #endif @@ -585,11 +282,7 @@ parse_bytes(PyObject UNUSED *self, PyObject *args) { } PyObject* -#ifdef DUMP_COMMANDS -read_bytes_dump(PyObject UNUSED *self, PyObject *args) { -#else -read_bytes(PyObject UNUSED *self, PyObject *args) { -#endif +FNAME(read_bytes)(PyObject UNUSED *self, PyObject *args) { PyObject *dump_callback = NULL; Py_ssize_t len; Screen *screen; @@ -613,4 +306,5 @@ read_bytes(PyObject UNUSED *self, PyObject *args) { if(len > 0) { Py_RETURN_TRUE; } Py_RETURN_FALSE; } - +#undef FNAME +// }}} diff --git a/kitty/screen.c b/kitty/screen.c index 46d56c4cf..cf8622413 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -23,9 +23,6 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { self = (Screen *)type->tp_alloc(type, 0); if (self != NULL) { - self->current_charset = 2; - self->g0_charset = translation_table('B'); - self->g1_charset = translation_table('0'); self->columns = columns; self->lines = lines; self->modes = empty_modes; self->utf8_state = 0; @@ -47,9 +44,6 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { void screen_reset(Screen *self) { if (self->linebuf == self->alt_linebuf) screen_toggle_screen_buffer(self); 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; @@ -126,52 +120,22 @@ dealloc(Screen* self) { // Draw text {{{ -void screen_shift_out(Screen *self, uint8_t UNUSED ch) { - self->current_charset = 1; - self->utf8_state = 0; -} - -void screen_shift_in(Screen *self, uint8_t UNUSED ch) { - self->current_charset = 0; - self->utf8_state = 0; -} - -void screen_define_charset(Screen *self, uint8_t code, uint8_t mode) { - switch(mode) { - case '(': - self->g0_charset = translation_table(code); - break; - default: - self->g1_charset = translation_table(code); - break; - } -} - -void screen_select_other_charset(Screen *self, uint8_t code, uint8_t UNUSED unused) { - switch(code) { - case '@': - self->current_charset = 0; - break; - default: - self->current_charset = 2; - self->utf8_state = 0; - } -} - -static inline unsigned 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 void -draw_codepoint(Screen UNUSED *self, uint32_t ch) { +void +screen_draw(Screen *self, uint32_t ch) { if (is_ignored_char(ch)) return; + unsigned int x = self->cursor->x, y = self->cursor->y; unsigned int char_width = safe_wcwidth(ch); if (self->columns - self->cursor->x < char_width) { if (self->modes.mDECAWM) { - screen_carriage_return(self, 13); - screen_linefeed(self, 10); + screen_carriage_return(self); + screen_linefeed(self); self->linebuf->continued_map[self->cursor->y] = true; } else { self->cursor->x = self->columns - char_width; @@ -202,43 +166,9 @@ 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); } } -} - -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: - draw_codepoint(self, codepoint); - break; - case UTF8_REJECT: - self->utf8_state = UTF8_ACCEPT; - if (prev != UTF8_ACCEPT) i--; - break; - } - } -} - -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++) { - draw_codepoint(self, table[buf[i]]); - } -} - -void screen_draw(Screen *self, uint8_t *buf, unsigned int buflen) { - unsigned int x = self->cursor->x, y = self->cursor->y; - switch(self->current_charset) { - case 0: - screen_draw_charset(self, self->g0_charset, buf, buflen); break; - case 1: - screen_draw_charset(self, self->g1_charset, buf, buflen); break; - default: - screen_draw_utf8(self, buf, buflen); break; - } if (x != self->cursor->x || y != self->cursor->y) tracker_cursor_changed(self->change_tracker); } + // }}} // Graphics {{{ @@ -451,10 +381,10 @@ void screen_reset_mode(Screen *self, unsigned int mode) { // Cursor {{{ -void screen_backspace(Screen *self, uint8_t UNUSED ch) { +void screen_backspace(Screen *self) { screen_cursor_back(self, 1, -1); } -void screen_tab(Screen *self, uint8_t UNUSED ch) { +void screen_tab(Screen *self) { // Move to the next tab space, or the end of the screen if there aren't anymore left. unsigned int found = 0; for (unsigned int i = self->cursor->x + 1; i < self->columns; i++) { @@ -556,16 +486,16 @@ void screen_reverse_index(Screen *self) { } -void screen_carriage_return(Screen *self, uint8_t UNUSED ch) { +void screen_carriage_return(Screen *self) { if (self->cursor->x != 0) { self->cursor->x = 0; tracker_cursor_changed(self->change_tracker); } } -void screen_linefeed(Screen *self, uint8_t UNUSED ch) { +void screen_linefeed(Screen *self) { screen_index(self); - if (self->modes.mLNM) screen_carriage_return(self, 13); + if (self->modes.mLNM) screen_carriage_return(self); screen_ensure_bounds(self, false); } @@ -586,9 +516,6 @@ void screen_save_cursor(Screen *self) { SavepointBuffer *pts = self->linebuf == self->main_linebuf ? &self->main_savepoints : &self->alt_savepoints; Savepoint *sp = savepoints_push(pts); cursor_copy_to(self->cursor, &(sp->cursor)); - sp->g0_charset = self->g0_charset; - sp->g1_charset = self->g1_charset; - sp->current_charset = self->current_charset; sp->mDECOM = self->modes.mDECOM; sp->mDECAWM = self->modes.mDECAWM; sp->utf8_state = self->utf8_state; @@ -601,13 +528,7 @@ void screen_restore_cursor(Screen *self) { screen_cursor_position(self, 1, 1); tracker_cursor_changed(self->change_tracker); screen_reset_mode(self, DECOM); - self->current_charset = 2; - self->g0_charset = translation_table('B'); - self->g1_charset = translation_table('0'); } else { - self->g0_charset = sp->g0_charset; - self->g1_charset = sp->g1_charset; - self->current_charset = sp->current_charset; self->utf8_state = sp->utf8_state; if (sp->mDECOM) screen_set_mode(self, DECOM); if (sp->mDECAWM) screen_set_mode(self, DECAWM); @@ -739,7 +660,7 @@ void screen_insert_lines(Screen *self, unsigned int count) { if (top <= self->cursor->y && 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); + screen_carriage_return(self); } } @@ -749,7 +670,7 @@ void screen_delete_lines(Screen *self, unsigned int count) { if (top <= self->cursor->y && 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); + screen_carriage_return(self); } } @@ -794,10 +715,11 @@ void screen_erase_characters(Screen *self, unsigned int count) { // Device control {{{ -void screen_bell(Screen UNUSED *self, uint8_t ch) { +void screen_bell(Screen UNUSED *self) { FILE *f = fopen("/dev/tty", "w"); + static const char *bell = "\007"; if (f != NULL) { - fwrite(&ch, 1, 1, f); + fwrite(bell, 1, 1, f); fclose(f); } } @@ -901,11 +823,14 @@ line(Screen *self, PyObject *val) { } static PyObject* -draw(Screen *self, PyObject *args) { +draw(Screen *self, PyObject *src) { #define draw_doc "" - Py_buffer pybuf; - if(!PyArg_ParseTuple(args, "y*", &pybuf)) return NULL; - screen_draw(self, pybuf.buf, pybuf.len); + if (!PyUnicode_Check(src)) { PyErr_SetString(PyExc_TypeError, "A unicode string is required"); return NULL; } + if (PyUnicode_READY(src) != 0) { return PyErr_NoMemory(); } + int kind = PyUnicode_KIND(src); + void *buf = PyUnicode_DATA(src); + Py_ssize_t sz = PyUnicode_GET_LENGTH(src); + for (Py_ssize_t i = 0; i < sz; i++) screen_draw(self, PyUnicode_READ(kind, buf, i)); Py_RETURN_NONE; } @@ -1097,7 +1022,7 @@ COUNT_WRAP(cursor_forward) static PyMethodDef methods[] = { METHOD(line, METH_O) - METHOD(draw, METH_VARARGS) + METHOD(draw, METH_O) METHOD(set_mode, METH_VARARGS) METHOD(reset_mode, METH_VARARGS) METHOD(enable_focus_tracking, METH_NOARGS) @@ -1139,7 +1064,6 @@ static PyMemberDef members[] = { {"columns", T_UINT, offsetof(Screen, columns), READONLY, "columns"}, {"margin_top", T_UINT, offsetof(Screen, margin_top), READONLY, "margin_top"}, {"margin_bottom", T_UINT, offsetof(Screen, margin_bottom), READONLY, "margin_bottom"}, - {"current_charset", T_UINT, offsetof(Screen, current_charset), READONLY, "current_charset"}, {NULL} }; diff --git a/kitty_tests/parser.py b/kitty_tests/parser.py index 01eca60ae..ef09d3b2d 100644 --- a/kitty_tests/parser.py +++ b/kitty_tests/parser.py @@ -36,7 +36,7 @@ class Callbacks: self.wtcbuf = self.iconbuf = self.titlebuf = self.colorbuf = b'' -class TestScreen(BaseTest): +class TestParser(BaseTest): def parse_bytes_dump(self, s, x, *cmds): cd = CmdDump()