diff --git a/kitty/control-codes.h b/kitty/control-codes.h index 461ba24c9..0fcbc5fac 100644 --- a/kitty/control-codes.h +++ b/kitty/control-codes.h @@ -63,3 +63,45 @@ // *Operating system command*. #define OSC 0x9d + +// Sharp control codes + +// Align display +#define DECALN '8' + +// Esc control codes + +// *Reset*. +#define 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' + +// *Next line*: Same as :data:`pyte.control.LF`. +#define NEL 'E' + +// Tabulation set: Set a horizontal tab stop at cursor position. +#define 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' + +// Save cursor: Save cursor position, character attribute (graphic +// rendition), character set, and origin mode selection (see +// :data:`DECRC`). +#define 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' + +// Set normal keypad mode +#define DECPNM '>' + +// Set alternate keypad mode +#define DECPAM '=' + + diff --git a/kitty/data-types.h b/kitty/data-types.h index eb80e5104..be9246449 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -225,6 +225,8 @@ typedef struct { } Savepoint; PyTypeObject Savepoint_Type; +#define PARSER_BUF_SZ 8192 + typedef struct { PyObject_HEAD @@ -239,8 +241,8 @@ typedef struct { ChangeTracker *change_tracker; ScreenModes modes; - uint8_t parser_buf[8192]; - unsigned int parser_state, parser_text_start; + uint8_t parser_buf[PARSER_BUF_SZ]; + unsigned int parser_state, parser_text_start, parser_buf_pos; bool parser_has_pending_text; } Screen; @@ -304,6 +306,13 @@ 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); +void screen_reset(Screen *self); +void screen_set_tab_stop(Screen *self); #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 0c84c823c..dd15cb845 100644 --- a/kitty/parser.c +++ b/kitty/parser.c @@ -22,6 +22,8 @@ static inline void read_##name(Screen *screen, uint8_t UNUSED *buf, unsigned int UNUSED buflen, unsigned int UNUSED *pos) #endif +#define SET_STATE(state) screen->parser_state = state; screen->parser_buf_pos = 0; + // Parse text {{{ HANDLER(text) { uint8_t ch; @@ -39,40 +41,39 @@ HANDLER(text) { #define CALL_SCREEN_HANDLER(name) \ DRAW_TEXT; \ Py_XDECREF(PyObject_CallFunction(dump_callback, "sC", #name, (int)ch)); PyErr_Clear(); \ - screen_##name(screen, ch); break; + name(screen, ch); break; #else #define CALL_SCREEN_HANDLER(name) \ DRAW_TEXT; \ - screen_##name(screen, ch); break; + name(screen, ch); break; #endif -#define CHANGE_PARSER_STATE(state) screen->parser_state = state; return; switch(ch) { case BEL: - CALL_SCREEN_HANDLER(bell); + CALL_SCREEN_HANDLER(screen_bell); case BS: - CALL_SCREEN_HANDLER(backspace); + CALL_SCREEN_HANDLER(screen_backspace); case HT: - CALL_SCREEN_HANDLER(tab); + CALL_SCREEN_HANDLER(screen_tab); case LF: case VT: case FF: - CALL_SCREEN_HANDLER(linefeed); + CALL_SCREEN_HANDLER(screen_linefeed); case CR: - CALL_SCREEN_HANDLER(carriage_return); + CALL_SCREEN_HANDLER(screen_carriage_return); case SO: - CALL_SCREEN_HANDLER(shift_out); + CALL_SCREEN_HANDLER(screen_shift_out); case SI: - CALL_SCREEN_HANDLER(shift_in); + CALL_SCREEN_HANDLER(screen_shift_in); case ESC: - CHANGE_PARSER_STATE(ESC_STATE); + DRAW_TEXT; SET_STATE(ESC_STATE); return; case CSI: - CHANGE_PARSER_STATE(CSI_STATE); + DRAW_TEXT; SET_STATE(CSI_STATE); return; + case OSC: + DRAW_TEXT; SET_STATE(OSC_STATE); return; case NUL: case DEL: - break; - case OSC: - CHANGE_PARSER_STATE(OSC_STATE); + break; // no-op default: if (!screen->parser_has_pending_text) { screen->parser_has_pending_text = true; @@ -85,9 +86,104 @@ HANDLER(text) { // }}} // Parse ESC {{{ -HANDLER(esc) { - screen->parser_state = NORMAL_STATE; + +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) { +#ifdef DUMP_COMMANDS +#define CALL_ED(name) \ + Py_XDECREF(PyObject_CallFunction(dump_callback, "s", #name)); PyErr_Clear(); \ + name(screen); break; +#else +#define CALL_ED(name) name(screen); break; +#endif + switch (ch) { + case RIS: + CALL_ED(screen_reset); + case IND: + CALL_ED(screen_index); + case NEL: + CALL_ED(screen_linefeed2); + case RI: + CALL_ED(screen_reverse_index); + 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); +#ifdef DUMP_COMMANDS + default: + Py_XDECREF(PyObject_CallFunction(dump_callback, "sB", "Unknown char in escape_dispatch: ", ch)); PyErr_Clear(); +#endif + } } + +static inline void sharp_dispatch(Screen *screen, uint8_t ch, PyObject UNUSED *dump_callback) { + switch(ch) { + case DECALN: +#ifdef DUMP_COMMANDS + Py_XDECREF(PyObject_CallFunction(dump_callback, "s", "screen_alignment_display")); PyErr_Clear(); +#endif + screen_alignment_display(screen); + break; +#ifdef DUMP_COMMANDS + default: + Py_XDECREF(PyObject_CallFunction(dump_callback, "sB", "Unknown char in sharp_dispatch: ", ch)); PyErr_Clear(); +#endif + } +} + +HANDLER(esc) { +#ifdef DUMP_COMMANDS +#define ESC_DISPATCH(which, extra) which(screen, ch, extra); SET_STATE(NORMAL_STATE); \ + Py_XDECREF(PyObject_CallFunction(dump_callback, "sCC", #which, (int)ch, (int)(extra))); PyErr_Clear(); \ + return; +#else +#define ESC_DISPATCH(which, extra) which(screen, ch, extra); SET_STATE(NORMAL_STATE); return; +#endif +#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; + } +} + // }}} // Parse CSI {{{ diff --git a/kitty/screen.c b/kitty/screen.c index c1cb4f545..80b452d14 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -88,16 +88,38 @@ void screen_bell(Screen UNUSED *self, uint8_t ch) { // {{{ // Draw text {{{ -void screen_shift_out(Screen UNUSED *self, uint8_t UNUSED ch) { +void screen_shift_out(Screen *self, uint8_t UNUSED ch) { self->current_charset = 1; self->utf8_state = 0; } -void screen_shift_in(Screen UNUSED *self, uint8_t UNUSED ch) { +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) { int ans = wcwidth(ch); if (ans < 0) ans = 1; @@ -189,6 +211,16 @@ void screen_change_default_color(Screen *self, unsigned int which, uint32_t col) if (PyErr_Occurred()) PyErr_Print(); PyErr_Clear(); } + +void screen_alignment_display(Screen *self) { + // http://www.vt100.net/docs/vt510-rm/DECALN.html + screen_cursor_position(self, 1, 1); + self->margin_top = 0; self->margin_bottom = self->columns - 1; + for (unsigned int y = 0; y < self->linebuf->ynum; y++) { + linebuf_init_line(self->linebuf, y); + line_clear_text(self->linebuf->line, 0, self->linebuf->xnum, 'E'); + } +} // }}} // Modes {{{ @@ -313,10 +345,10 @@ void screen_reset_mode(Screen *self, int mode) { // Cursor {{{ -void screen_backspace(Screen UNUSED *self, uint8_t UNUSED ch) { +void screen_backspace(Screen *self, uint8_t UNUSED ch) { screen_cursor_back(self, 1, -1); } -void screen_tab(Screen UNUSED *self, uint8_t UNUSED ch) { +void screen_tab(Screen *self, uint8_t UNUSED ch) { // 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++) { @@ -329,6 +361,11 @@ void screen_tab(Screen UNUSED *self, uint8_t UNUSED ch) { } } +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; +} + void screen_cursor_back(Screen *self, unsigned int count/*=1*/, int move_direction/*=-1*/) { int x = self->cursor->x; if (count == 0) count = 1; diff --git a/kitty_tests/parser.py b/kitty_tests/parser.py index 0c3ed7c7c..cb17b266c 100644 --- a/kitty_tests/parser.py +++ b/kitty_tests/parser.py @@ -2,6 +2,8 @@ # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2016, Kovid Goyal +from functools import partial + from . import BaseTest from kitty.fast_data_types import parse_bytes, parse_bytes_dump @@ -15,22 +17,23 @@ class CmdDump(list): class TestScreen(BaseTest): + def parse_buytes_dump(self, s, x, *cmds): + cd = CmdDump() + if isinstance(x, str): + x = x.encode('utf-8') + parse_bytes_dump(s, x, cd) + self.ae(tuple(cd), cmds) + def test_simple_parsing(self): s = self.create_screen() - - def pb(x, *cmds): - cd = CmdDump() - if isinstance(x, str): - x = x.encode('utf-8') - parse_bytes_dump(s, x, cd) - self.ae(tuple(cd), cmds) + pb = partial(self.parse_buytes_dump, s) pb('12') self.ae(str(s.line(0)), '12 ') pb('3456') self.ae(str(s.line(0)), '12345') self.ae(str(s.line(1)), '6 ') - pb(b'\n123\n\r45', ('linefeed', '\n'), ('linefeed', '\n'), ('carriage_return', '\r')) + pb(b'\n123\n\r45', ('screen_linefeed', '\n'), ('screen_linefeed', '\n'), ('screen_carriage_return', '\r')) self.ae(str(s.line(1)), '6 ') self.ae(str(s.line(2)), ' 123 ') self.ae(str(s.line(3)), '45 ') @@ -40,3 +43,13 @@ class TestScreen(BaseTest): self.ae(str(s.line(3)), 'ßxyz1') pb('ニチ '.encode('utf-8')) self.ae(str(s.line(4)), 'ニチ ') + + def test_esc_codes(self): + s = self.create_screen() + pb = partial(self.parse_buytes_dump, s) + pb('12\033Da', ('screen_index',)) + self.ae(str(s.line(0)), '12 ') + self.ae(str(s.line(1)), ' a ') + pb('\033x', ('Unknown char in escape_dispatch: ', ord('x'))) + pb('\033c123', ('screen_reset',)) + self.ae(str(s.line(0)), '123 ')