Start work on CSI parser
Also cleaup error reporting and command dumping macros
This commit is contained in:
parent
5208500eb9
commit
f6faecbaaa
@ -65,11 +65,13 @@
|
|||||||
#define OSC 0x9d
|
#define OSC 0x9d
|
||||||
|
|
||||||
// Sharp control codes
|
// Sharp control codes
|
||||||
|
// -------------------
|
||||||
|
|
||||||
// Align display
|
// Align display
|
||||||
#define DECALN '8'
|
#define DECALN '8'
|
||||||
|
|
||||||
// Esc control codes
|
// Esc control codes
|
||||||
|
// ------------------
|
||||||
|
|
||||||
// *Reset*.
|
// *Reset*.
|
||||||
#define RIS 'c'
|
#define RIS 'c'
|
||||||
@ -104,4 +106,117 @@
|
|||||||
// Set alternate keypad mode
|
// Set alternate keypad mode
|
||||||
#define DECPAM '='
|
#define DECPAM '='
|
||||||
|
|
||||||
|
// ECMA-48 CSI sequences.
|
||||||
|
// ---------------------
|
||||||
|
|
||||||
|
// *Insert character*: Insert the indicated # of blank characters.
|
||||||
|
#define ICH '@'
|
||||||
|
|
||||||
|
// *Cursor up*: Move cursor up the indicated # of lines in same column.
|
||||||
|
// Cursor stops at top margin.
|
||||||
|
#define CUU 'A'
|
||||||
|
|
||||||
|
// *Cursor down*: Move cursor down the indicated # of lines in same
|
||||||
|
// column. Cursor stops at bottom margin.
|
||||||
|
#define CUD 'B'
|
||||||
|
|
||||||
|
// *Cursor forward*: Move cursor right the indicated # of columns.
|
||||||
|
// Cursor stops at right margin.
|
||||||
|
#define CUF 'C'
|
||||||
|
|
||||||
|
// *Cursor back*: Move cursor left the indicated # of columns. Cursor
|
||||||
|
// stops at left margin.
|
||||||
|
#define CUB 'D'
|
||||||
|
|
||||||
|
// *Cursor next line*: Move cursor down the indicated # of lines to
|
||||||
|
// column 1.
|
||||||
|
#define CNL 'E'
|
||||||
|
|
||||||
|
// *Cursor previous line*: Move cursor up the indicated # of lines to
|
||||||
|
// column 1.
|
||||||
|
#define CPL 'F'
|
||||||
|
|
||||||
|
// *Cursor horizontal align*: Move cursor to the indicated column in
|
||||||
|
// current line.
|
||||||
|
#define CHA 'G'
|
||||||
|
|
||||||
|
// *Cursor position*: Move cursor to the indicated line, column (origin
|
||||||
|
// at ``1, 1``).
|
||||||
|
#define CUP 'H'
|
||||||
|
|
||||||
|
// *Erase data* (default: from cursor to end of line).
|
||||||
|
#define ED 'J'
|
||||||
|
|
||||||
|
// *Erase in line* (default: from cursor to end of line).
|
||||||
|
#define EL 'K'
|
||||||
|
|
||||||
|
// *Insert line*: Insert the indicated # of blank lines, starting from
|
||||||
|
// the current line. Lines displayed below cursor move down. Lines moved
|
||||||
|
// past the bottom margin are lost.
|
||||||
|
#define IL 'L'
|
||||||
|
|
||||||
|
// *Delete line*: Delete the indicated # of lines, starting from the
|
||||||
|
// current line. As lines are deleted, lines displayed below cursor
|
||||||
|
// move up. Lines added to bottom of screen have spaces with same
|
||||||
|
// character attributes as last line move up.
|
||||||
|
#define DL 'M'
|
||||||
|
|
||||||
|
// *Delete character*: Delete the indicated # of characters on the
|
||||||
|
// current line. When character is deleted, all characters to the right
|
||||||
|
// of cursor move left.
|
||||||
|
#define DCH 'P'
|
||||||
|
|
||||||
|
// *Erase character*: Erase the indicated # of characters on the
|
||||||
|
// current line.
|
||||||
|
#define ECH 'X'
|
||||||
|
|
||||||
|
// *Horizontal position relative*: Same as :data:`CUF`.
|
||||||
|
#define HPR 'a'
|
||||||
|
|
||||||
|
// *Device Attributes*.
|
||||||
|
#define DA 'c'
|
||||||
|
|
||||||
|
// *Vertical position adjust*: Move cursor to the indicated line,
|
||||||
|
// current column.
|
||||||
|
#define VPA 'd'
|
||||||
|
|
||||||
|
// *Vertical position relative*: Same as :data:`CUD`.
|
||||||
|
#define VPR 'e'
|
||||||
|
|
||||||
|
// *Horizontal / Vertical position*: Same as :data:`CUP`.
|
||||||
|
#define HVP 'f'
|
||||||
|
|
||||||
|
// *Tabulation clear*: Clears a horizontal tab stop at cursor position.
|
||||||
|
#define TBC 'g'
|
||||||
|
|
||||||
|
// *Set mode*.
|
||||||
|
#define SM 'h'
|
||||||
|
|
||||||
|
// *Reset mode*.
|
||||||
|
#define RM 'l'
|
||||||
|
|
||||||
|
// *Select graphics rendition*: The terminal can display the following
|
||||||
|
// character attributes that change the character display without
|
||||||
|
// changing the character (see :mod:`pyte.graphics`).
|
||||||
|
#define SGR 'm'
|
||||||
|
|
||||||
|
// *Device status report*.
|
||||||
|
#define DSR 'n'
|
||||||
|
|
||||||
|
// Soft reset
|
||||||
|
#define DECSTR 'p'
|
||||||
|
|
||||||
|
// *Select top and bottom margins*: Selects margins, defining the
|
||||||
|
// scrolling region; parameters are top and bottom line. If called
|
||||||
|
// without any arguments, whole screen is used.
|
||||||
|
#define DECSTBM 'r'
|
||||||
|
|
||||||
|
// *Horizontal position adjust*: Same as :data:`CHA`.
|
||||||
|
#define HPA '\''
|
||||||
|
|
||||||
|
|
||||||
|
// Misc sequences
|
||||||
|
// ----------------
|
||||||
|
|
||||||
|
// Change cursor shape/blink
|
||||||
|
#define DECSCUSR 'q'
|
||||||
|
|||||||
138
kitty/parser.c
138
kitty/parser.c
@ -5,6 +5,7 @@
|
|||||||
* Distributed under terms of the GPL3 license.
|
* Distributed under terms of the GPL3 license.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
#include "data-types.h"
|
#include "data-types.h"
|
||||||
#include "control-codes.h"
|
#include "control-codes.h"
|
||||||
|
|
||||||
@ -14,17 +15,63 @@
|
|||||||
#define OSC_STATE 3
|
#define OSC_STATE 3
|
||||||
#define DCS_STATE 4
|
#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':
|
||||||
|
|
||||||
#ifdef DUMP_COMMANDS
|
#ifdef DUMP_COMMANDS
|
||||||
|
static void _report_error(PyObject *dump_callback, const char *fmt, ...) {
|
||||||
|
va_list argptr;
|
||||||
|
va_start(argptr, fmt);
|
||||||
|
PyObject *temp = PyUnicode_FromFormatV(fmt, argptr);
|
||||||
|
va_end(argptr);
|
||||||
|
if (temp != NULL) {
|
||||||
|
Py_XDECREF(PyObject_CallFunctionObjArgs(dump_callback, temp, NULL)); PyErr_Clear();
|
||||||
|
Py_CLEAR(temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define REPORT_ERROR(...) _report_error(dump_callback, __VA_ARGS__);
|
||||||
|
|
||||||
|
#define REPORT_COMMAND0(name) \
|
||||||
|
Py_XDECREF(PyObject_CallFunction(dump_callback, "s", #name)); PyErr_Clear();
|
||||||
|
|
||||||
|
#define REPORT_COMMAND1(name, x) \
|
||||||
|
Py_XDECREF(PyObject_CallFunction(dump_callback, "si", #name, (int)x)); PyErr_Clear();
|
||||||
|
|
||||||
|
#define REPORT_COMMAND2(name, x, y) \
|
||||||
|
Py_XDECREF(PyObject_CallFunction(dump_callback, "sii", #name, (int)x, (int)y)); PyErr_Clear();
|
||||||
|
|
||||||
#define HANDLER(name) \
|
#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)
|
static inline void read_##name(Screen *screen, uint8_t UNUSED *buf, unsigned int UNUSED buflen, unsigned int UNUSED *pos, PyObject UNUSED *dump_callback)
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
|
#define REPORT_ERROR(...) fprintf(stderr, "[PARSE ERROR] "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n");
|
||||||
|
|
||||||
|
#define REPORT_COMMAND0(name)
|
||||||
|
#define REPORT_COMMAND1(name, x)
|
||||||
|
#define REPORT_COMMAND2(name, x, y)
|
||||||
|
|
||||||
#define HANDLER(name) \
|
#define HANDLER(name) \
|
||||||
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;
|
#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;
|
||||||
|
|
||||||
@ -37,16 +84,9 @@ HANDLER(text) {
|
|||||||
screen->parser_text_start = 0; \
|
screen->parser_text_start = 0; \
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DUMP_COMMANDS
|
|
||||||
#define CALL_SCREEN_HANDLER(name) \
|
#define CALL_SCREEN_HANDLER(name) \
|
||||||
DRAW_TEXT; \
|
DRAW_TEXT; REPORT_COMMAND1(name, ch); \
|
||||||
Py_XDECREF(PyObject_CallFunction(dump_callback, "sC", #name, (int)ch)); PyErr_Clear(); \
|
|
||||||
name(screen, ch); break;
|
name(screen, ch); break;
|
||||||
#else
|
|
||||||
#define CALL_SCREEN_HANDLER(name) \
|
|
||||||
DRAW_TEXT; \
|
|
||||||
name(screen, ch); break;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
switch(ch) {
|
switch(ch) {
|
||||||
case BEL:
|
case BEL:
|
||||||
@ -83,6 +123,7 @@ HANDLER(text) {
|
|||||||
}
|
}
|
||||||
DRAW_TEXT;
|
DRAW_TEXT;
|
||||||
}
|
}
|
||||||
|
#define moo 1
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
// Parse ESC {{{
|
// Parse ESC {{{
|
||||||
@ -90,13 +131,7 @@ HANDLER(text) {
|
|||||||
static inline void screen_linefeed2(Screen *screen) { screen_linefeed(screen, '\n'); }
|
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) {
|
static inline void escape_dispatch(Screen *screen, uint8_t ch, PyObject UNUSED *dump_callback) {
|
||||||
#ifdef DUMP_COMMANDS
|
#define CALL_ED(name) REPORT_COMMAND0(name); name(screen); break;
|
||||||
#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) {
|
switch (ch) {
|
||||||
case RIS:
|
case RIS:
|
||||||
CALL_ED(screen_reset);
|
CALL_ED(screen_reset);
|
||||||
@ -116,36 +151,24 @@ static inline void escape_dispatch(Screen *screen, uint8_t ch, PyObject UNUSED *
|
|||||||
CALL_ED(screen_normal_keypad_mode);
|
CALL_ED(screen_normal_keypad_mode);
|
||||||
case DECPAM:
|
case DECPAM:
|
||||||
CALL_ED(screen_alternate_keypad_mode);
|
CALL_ED(screen_alternate_keypad_mode);
|
||||||
#ifdef DUMP_COMMANDS
|
|
||||||
default:
|
default:
|
||||||
Py_XDECREF(PyObject_CallFunction(dump_callback, "sB", "Unknown char in escape_dispatch: ", ch)); PyErr_Clear();
|
REPORT_ERROR("%s%d", "Unknown char in escape_dispatch: ", ch);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void sharp_dispatch(Screen *screen, uint8_t ch, PyObject UNUSED *dump_callback) {
|
static inline void sharp_dispatch(Screen *screen, uint8_t ch, PyObject UNUSED *dump_callback) {
|
||||||
switch(ch) {
|
switch(ch) {
|
||||||
case DECALN:
|
case DECALN:
|
||||||
#ifdef DUMP_COMMANDS
|
REPORT_COMMAND0(screen_alignment_display);
|
||||||
Py_XDECREF(PyObject_CallFunction(dump_callback, "s", "screen_alignment_display")); PyErr_Clear();
|
|
||||||
#endif
|
|
||||||
screen_alignment_display(screen);
|
screen_alignment_display(screen);
|
||||||
break;
|
break;
|
||||||
#ifdef DUMP_COMMANDS
|
|
||||||
default:
|
default:
|
||||||
Py_XDECREF(PyObject_CallFunction(dump_callback, "sB", "Unknown char in sharp_dispatch: ", ch)); PyErr_Clear();
|
REPORT_ERROR("%s%d", "Unknown char in sharp_dispatch: ", ch);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HANDLER(esc) {
|
HANDLER(esc) {
|
||||||
#ifdef DUMP_COMMANDS
|
#define ESC_DISPATCH(which, extra) REPORT_COMMAND2(which, ch, extra); which(screen, ch, extra); SET_STATE(NORMAL_STATE); return;
|
||||||
#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
|
#ifdef DUMP_COMMANDS
|
||||||
#define ESC_DELEGATE(which) which(screen, ch, dump_callback); SET_STATE(NORMAL_STATE); return;
|
#define ESC_DELEGATE(which) which(screen, ch, dump_callback); SET_STATE(NORMAL_STATE); return;
|
||||||
#else
|
#else
|
||||||
@ -188,7 +211,58 @@ HANDLER(esc) {
|
|||||||
|
|
||||||
// Parse CSI {{{
|
// Parse CSI {{{
|
||||||
HANDLER(csi) {
|
HANDLER(csi) {
|
||||||
screen->parser_state = NORMAL_STATE;
|
#define CALL_BASIC_HANDLER(name) REPORT_COMMAND1(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
|
||||||
|
|
||||||
|
uint8_t ch = buf[(*pos)++];
|
||||||
|
switch(screen->parser_buf_pos) {
|
||||||
|
case 0: // CSI starting
|
||||||
|
screen->parser_buf[0] = 0;
|
||||||
|
screen->parser_buf[1] = 0;
|
||||||
|
switch(ch) {
|
||||||
|
IS_DIGIT
|
||||||
|
screen->parser_buf_pos = 2;
|
||||||
|
screen->parser_buf[1] = ch;
|
||||||
|
break;
|
||||||
|
case '?':
|
||||||
|
case '>':
|
||||||
|
case '!':
|
||||||
|
screen->parser_buf[0] = ch; screen->parser_buf_pos = 1;
|
||||||
|
break;
|
||||||
|
HANDLE_BASIC_CH
|
||||||
|
default:
|
||||||
|
REPORT_ERROR("%s%d", "Invalid first character for CSI: ", (int)ch);
|
||||||
|
SET_STATE(NORMAL_STATE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,8 @@ from kitty.fast_data_types import parse_bytes, parse_bytes_dump
|
|||||||
class CmdDump(list):
|
class CmdDump(list):
|
||||||
|
|
||||||
def __call__(self, *a):
|
def __call__(self, *a):
|
||||||
|
if len(a) == 1:
|
||||||
|
a = a[0]
|
||||||
self.append(a)
|
self.append(a)
|
||||||
|
|
||||||
|
|
||||||
@ -33,7 +35,7 @@ class TestScreen(BaseTest):
|
|||||||
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', ('screen_linefeed', '\n'), ('screen_linefeed', '\n'), ('screen_carriage_return', '\r'))
|
pb(b'\n123\n\r45', ('screen_linefeed', ord('\n')), ('screen_linefeed', ord('\n')), ('screen_carriage_return', ord('\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 ')
|
||||||
@ -47,9 +49,9 @@ class TestScreen(BaseTest):
|
|||||||
def test_esc_codes(self):
|
def test_esc_codes(self):
|
||||||
s = self.create_screen()
|
s = self.create_screen()
|
||||||
pb = partial(self.parse_buytes_dump, s)
|
pb = partial(self.parse_buytes_dump, s)
|
||||||
pb('12\033Da', ('screen_index',))
|
pb('12\033Da', 'screen_index')
|
||||||
self.ae(str(s.line(0)), '12 ')
|
self.ae(str(s.line(0)), '12 ')
|
||||||
self.ae(str(s.line(1)), ' a ')
|
self.ae(str(s.line(1)), ' a ')
|
||||||
pb('\033x', ('Unknown char in escape_dispatch: ', ord('x')))
|
pb('\033x', 'Unknown char in escape_dispatch: %d' % ord('x'))
|
||||||
pb('\033c123', ('screen_reset',))
|
pb('\033c123', 'screen_reset')
|
||||||
self.ae(str(s.line(0)), '123 ')
|
self.ae(str(s.line(0)), '123 ')
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user