mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-19 15:04:50 +02:00
Implement basic ESC code handling
This commit is contained in:
@@ -63,3 +63,45 @@
|
|||||||
|
|
||||||
// *Operating system command*.
|
// *Operating system command*.
|
||||||
#define OSC 0x9d
|
#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 '='
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -225,6 +225,8 @@ typedef struct {
|
|||||||
} Savepoint;
|
} Savepoint;
|
||||||
PyTypeObject Savepoint_Type;
|
PyTypeObject Savepoint_Type;
|
||||||
|
|
||||||
|
#define PARSER_BUF_SZ 8192
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
PyObject_HEAD
|
PyObject_HEAD
|
||||||
|
|
||||||
@@ -239,8 +241,8 @@ typedef struct {
|
|||||||
ChangeTracker *change_tracker;
|
ChangeTracker *change_tracker;
|
||||||
ScreenModes modes;
|
ScreenModes modes;
|
||||||
|
|
||||||
uint8_t parser_buf[8192];
|
uint8_t parser_buf[PARSER_BUF_SZ];
|
||||||
unsigned int parser_state, parser_text_start;
|
unsigned int parser_state, parser_text_start, parser_buf_pos;
|
||||||
bool parser_has_pending_text;
|
bool parser_has_pending_text;
|
||||||
|
|
||||||
} Screen;
|
} Screen;
|
||||||
@@ -304,6 +306,13 @@ void screen_toggle_screen_buffer(Screen *self);
|
|||||||
void screen_normal_keypad_mode(Screen *self);
|
void screen_normal_keypad_mode(Screen *self);
|
||||||
void screen_alternate_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_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);
|
#define DECLARE_CH_SCREEN_HANDLER(name) void screen_##name(Screen *screen, uint8_t ch);
|
||||||
DECLARE_CH_SCREEN_HANDLER(bell)
|
DECLARE_CH_SCREEN_HANDLER(bell)
|
||||||
DECLARE_CH_SCREEN_HANDLER(backspace)
|
DECLARE_CH_SCREEN_HANDLER(backspace)
|
||||||
|
|||||||
130
kitty/parser.c
130
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)
|
static inline void read_##name(Screen *screen, uint8_t UNUSED *buf, unsigned int UNUSED buflen, unsigned int UNUSED *pos)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define SET_STATE(state) screen->parser_state = state; screen->parser_buf_pos = 0;
|
||||||
|
|
||||||
// Parse text {{{
|
// Parse text {{{
|
||||||
HANDLER(text) {
|
HANDLER(text) {
|
||||||
uint8_t ch;
|
uint8_t ch;
|
||||||
@@ -39,40 +41,39 @@ HANDLER(text) {
|
|||||||
#define CALL_SCREEN_HANDLER(name) \
|
#define CALL_SCREEN_HANDLER(name) \
|
||||||
DRAW_TEXT; \
|
DRAW_TEXT; \
|
||||||
Py_XDECREF(PyObject_CallFunction(dump_callback, "sC", #name, (int)ch)); PyErr_Clear(); \
|
Py_XDECREF(PyObject_CallFunction(dump_callback, "sC", #name, (int)ch)); PyErr_Clear(); \
|
||||||
screen_##name(screen, ch); break;
|
name(screen, ch); break;
|
||||||
#else
|
#else
|
||||||
#define CALL_SCREEN_HANDLER(name) \
|
#define CALL_SCREEN_HANDLER(name) \
|
||||||
DRAW_TEXT; \
|
DRAW_TEXT; \
|
||||||
screen_##name(screen, ch); break;
|
name(screen, ch); break;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define CHANGE_PARSER_STATE(state) screen->parser_state = state; return;
|
|
||||||
switch(ch) {
|
switch(ch) {
|
||||||
case BEL:
|
case BEL:
|
||||||
CALL_SCREEN_HANDLER(bell);
|
CALL_SCREEN_HANDLER(screen_bell);
|
||||||
case BS:
|
case BS:
|
||||||
CALL_SCREEN_HANDLER(backspace);
|
CALL_SCREEN_HANDLER(screen_backspace);
|
||||||
case HT:
|
case HT:
|
||||||
CALL_SCREEN_HANDLER(tab);
|
CALL_SCREEN_HANDLER(screen_tab);
|
||||||
case LF:
|
case LF:
|
||||||
case VT:
|
case VT:
|
||||||
case FF:
|
case FF:
|
||||||
CALL_SCREEN_HANDLER(linefeed);
|
CALL_SCREEN_HANDLER(screen_linefeed);
|
||||||
case CR:
|
case CR:
|
||||||
CALL_SCREEN_HANDLER(carriage_return);
|
CALL_SCREEN_HANDLER(screen_carriage_return);
|
||||||
case SO:
|
case SO:
|
||||||
CALL_SCREEN_HANDLER(shift_out);
|
CALL_SCREEN_HANDLER(screen_shift_out);
|
||||||
case SI:
|
case SI:
|
||||||
CALL_SCREEN_HANDLER(shift_in);
|
CALL_SCREEN_HANDLER(screen_shift_in);
|
||||||
case ESC:
|
case ESC:
|
||||||
CHANGE_PARSER_STATE(ESC_STATE);
|
DRAW_TEXT; SET_STATE(ESC_STATE); return;
|
||||||
case CSI:
|
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 NUL:
|
||||||
case DEL:
|
case DEL:
|
||||||
break;
|
break; // no-op
|
||||||
case OSC:
|
|
||||||
CHANGE_PARSER_STATE(OSC_STATE);
|
|
||||||
default:
|
default:
|
||||||
if (!screen->parser_has_pending_text) {
|
if (!screen->parser_has_pending_text) {
|
||||||
screen->parser_has_pending_text = true;
|
screen->parser_has_pending_text = true;
|
||||||
@@ -85,9 +86,104 @@ HANDLER(text) {
|
|||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
// Parse ESC {{{
|
// 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 {{{
|
// Parse CSI {{{
|
||||||
|
|||||||
@@ -88,16 +88,38 @@ void screen_bell(Screen UNUSED *self, uint8_t ch) { // {{{
|
|||||||
|
|
||||||
// Draw text {{{
|
// 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->current_charset = 1;
|
||||||
self->utf8_state = 0;
|
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->current_charset = 0;
|
||||||
self->utf8_state = 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);
|
int ans = wcwidth(ch);
|
||||||
if (ans < 0) ans = 1;
|
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();
|
if (PyErr_Occurred()) PyErr_Print();
|
||||||
PyErr_Clear();
|
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 {{{
|
// Modes {{{
|
||||||
@@ -313,10 +345,10 @@ void screen_reset_mode(Screen *self, int mode) {
|
|||||||
|
|
||||||
// Cursor {{{
|
// 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);
|
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.
|
// Move to the next tab space, or the end of the screen if there aren't anymore left.
|
||||||
unsigned int found = 0;
|
unsigned int found = 0;
|
||||||
for (unsigned int i = self->cursor->x + 1; i < self->columns; i++) {
|
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*/) {
|
void screen_cursor_back(Screen *self, unsigned int count/*=1*/, int move_direction/*=-1*/) {
|
||||||
int x = self->cursor->x;
|
int x = self->cursor->x;
|
||||||
if (count == 0) count = 1;
|
if (count == 0) count = 1;
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
# vim:fileencoding=utf-8
|
# vim:fileencoding=utf-8
|
||||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
from . import BaseTest
|
from . import BaseTest
|
||||||
|
|
||||||
from kitty.fast_data_types import parse_bytes, parse_bytes_dump
|
from kitty.fast_data_types import parse_bytes, parse_bytes_dump
|
||||||
@@ -15,22 +17,23 @@ class CmdDump(list):
|
|||||||
|
|
||||||
class TestScreen(BaseTest):
|
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):
|
def test_simple_parsing(self):
|
||||||
s = self.create_screen()
|
s = self.create_screen()
|
||||||
|
pb = partial(self.parse_buytes_dump, s)
|
||||||
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('12')
|
pb('12')
|
||||||
self.ae(str(s.line(0)), '12 ')
|
self.ae(str(s.line(0)), '12 ')
|
||||||
pb('3456')
|
pb('3456')
|
||||||
self.ae(str(s.line(0)), '12345')
|
self.ae(str(s.line(0)), '12345')
|
||||||
self.ae(str(s.line(1)), '6 ')
|
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(1)), '6 ')
|
||||||
self.ae(str(s.line(2)), ' 123 ')
|
self.ae(str(s.line(2)), ' 123 ')
|
||||||
self.ae(str(s.line(3)), '45 ')
|
self.ae(str(s.line(3)), '45 ')
|
||||||
@@ -40,3 +43,13 @@ class TestScreen(BaseTest):
|
|||||||
self.ae(str(s.line(3)), 'ßxyz1')
|
self.ae(str(s.line(3)), 'ßxyz1')
|
||||||
pb('ニチ '.encode('utf-8'))
|
pb('ニチ '.encode('utf-8'))
|
||||||
self.ae(str(s.line(4)), 'ニチ ')
|
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 ')
|
||||||
|
|||||||
Reference in New Issue
Block a user