Start work on dumping non-UTF-8 charset support

This commit is contained in:
Kovid Goyal 2016-11-23 15:33:08 +05:30
parent 4b0e8fcb49
commit f14e7037e2
5 changed files with 240 additions and 625 deletions

View File

@ -55,13 +55,13 @@
// *Delete*: Is ignored. // *Delete*: Is ignored.
#define DEL 0x7f #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 #define CSI 0x9b
// *String terminator*.
#define ST 0x9c #define ST 0x9c
// *Operating system command*.
#define OSC 0x9d #define OSC 0x9d
// Sharp control codes // Sharp control codes
@ -73,38 +73,43 @@
// Esc control codes // Esc control codes
// ------------------ // ------------------
#define ESC_DCS 'P'
#define ESC_OSC ']'
#define ESC_CSI '['
#define ESC_ST '\\'
// *Reset*. // *Reset*.
#define RIS 'c' #define ESC_RIS 'c'
// *Index*: Move cursor down one line in same column. If the cursor is // *Index*: Move cursor down one line in same column. If the cursor is
// at the bottom margin, the screen performs a scroll-up. // at the bottom margin, the screen performs a scroll-up.
#define IND 'D' #define ESC_IND 'D'
// *Next line*: Same as LF. // *Next line*: Same as LF.
#define NEL 'E' #define ESC_NEL 'E'
// Tabulation set: Set a horizontal tab stop at cursor position. // 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 // *Reverse index*: Move cursor up one line in same column. If the
// cursor is at the top margin, the screen performs a scroll-down. // 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 // Save cursor: Save cursor position, character attribute (graphic
// rendition), character set, and origin mode selection (see // rendition), character set, and origin mode selection (see
// :data:`DECRC`). // :data:`DECRC`).
#define DECSC '7' #define ESC_DECSC '7'
// *Restore cursor*: Restore previously saved cursor position, character // *Restore cursor*: Restore previously saved cursor position, character
// attribute (graphic rendition), character set, and origin mode // attribute (graphic rendition), character set, and origin mode
// selection. If none were saved, move cursor to home position. // selection. If none were saved, move cursor to home position.
#define DECRC '8' #define ESC_DECRC '8'
// Set normal keypad mode // Set normal keypad mode
#define DECPNM '>' #define ESC_DECPNM '>'
// Set alternate keypad mode // Set alternate keypad mode
#define DECPAM '=' #define ESC_DECPAM '='
// ECMA-48 CSI sequences. // ECMA-48 CSI sequences.
// --------------------- // ---------------------

View File

@ -227,8 +227,6 @@ PyTypeObject ScreenModes_Type;
#define SAVEPOINTS_SZ 256 #define SAVEPOINTS_SZ 256
typedef struct { typedef struct {
unsigned int current_charset;
uint16_t *g0_charset, *g1_charset;
uint32_t utf8_state; uint32_t utf8_state;
Cursor cursor; Cursor cursor;
bool mDECOM; bool mDECOM;
@ -243,16 +241,14 @@ typedef struct {
} SavepointBuffer; } SavepointBuffer;
#define PARSER_BUF_SZ 8192 #define PARSER_BUF_SZ (8 * 1024)
#define READ_BUF_SZ (1024*1024) #define READ_BUF_SZ (1024*1024)
typedef struct { typedef struct {
PyObject_HEAD PyObject_HEAD
unsigned int columns, lines, margin_top, margin_bottom; unsigned int columns, lines, margin_top, margin_bottom;
unsigned int current_charset;
uint32_t utf8_state; uint32_t utf8_state;
uint16_t *g0_charset, *g1_charset;
Cursor *cursor; Cursor *cursor;
SavepointBuffer main_savepoints, alt_savepoints; SavepointBuffer main_savepoints, alt_savepoints;
PyObject *callbacks; PyObject *callbacks;
@ -262,7 +258,7 @@ typedef struct {
ChangeTracker *change_tracker; ChangeTracker *change_tracker;
ScreenModes modes; 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; unsigned int parser_state, parser_text_start, parser_buf_pos;
bool parser_has_pending_text; bool parser_has_pending_text;
uint8_t read_buf[READ_BUF_SZ]; 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_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_line(Screen *, unsigned int, bool);
void screen_erase_in_display(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_ensure_bounds(Screen *self, bool use_margins);
void screen_toggle_screen_buffer(Screen *self); 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_alignment_display(Screen *self);
void screen_reverse_index(Screen *self); void screen_reverse_index(Screen *self);
void screen_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 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 select_graphic_rendition(Screen *self, unsigned int *params, unsigned int count);
void report_device_status(Screen *self, unsigned int which, bool UNUSED); 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(bell)
DECLARE_CH_SCREEN_HANDLER(backspace) DECLARE_CH_SCREEN_HANDLER(backspace)
DECLARE_CH_SCREEN_HANDLER(tab) DECLARE_CH_SCREEN_HANDLER(tab)
DECLARE_CH_SCREEN_HANDLER(linefeed) DECLARE_CH_SCREEN_HANDLER(linefeed)
DECLARE_CH_SCREEN_HANDLER(carriage_return) DECLARE_CH_SCREEN_HANDLER(carriage_return)
DECLARE_CH_SCREEN_HANDLER(shift_out)
DECLARE_CH_SCREEN_HANDLER(shift_in)

View File

@ -1,35 +1,13 @@
/* /*
* parser.c
* Copyright (C) 2016 Kovid Goyal <kovid at kovidgoyal.net> * Copyright (C) 2016 Kovid Goyal <kovid at kovidgoyal.net>
* *
* Distributed under terms of the GPL3 license. * Distributed under terms of the GPL3 license.
*/ */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include "data-types.h" #include "data-types.h"
#include "control-codes.h" #include "control-codes.h"
#define NORMAL_STATE 0 // Macros {{{
#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':
#ifdef DUMP_COMMANDS #ifdef DUMP_COMMANDS
static void _report_error(PyObject *dump_callback, const char *fmt, ...) { static void _report_error(PyObject *dump_callback, const char *fmt, ...) {
va_list argptr; 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_ERROR(...) _report_error(dump_callback, __VA_ARGS__);
#define REPORT_COMMAND1(name) \ #define REPORT_COMMAND1(name) \
@ -56,53 +36,27 @@ static void _report_error(PyObject *dump_callback, const char *fmt, ...) {
#define GET_MACRO(_1,_2,_3,NAME,...) NAME #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_COMMAND(...) GET_MACRO(__VA_ARGS__, REPORT_COMMAND3, REPORT_COMMAND2, REPORT_COMMAND1, SENTINEL)(__VA_ARGS__)
#define REPORT_DRAW(start, sz) \ #define REPORT_DRAW(ch) \
Py_XDECREF(PyObject_CallFunction(dump_callback, "sy#", "draw", start, sz)); PyErr_Clear(); Py_XDECREF(PyObject_CallFunction(dump_callback, "sC", "draw", ch)); 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)
#else #else
#define DUMP_UNUSED UNUSED
#define REPORT_ERROR(...) fprintf(stderr, "[PARSE ERROR] "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); #define REPORT_ERROR(...) fprintf(stderr, "[PARSE ERROR] "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n");
#define REPORT_COMMAND(...) #define REPORT_COMMAND(...)
#define REPORT_DRAW(...) #define REPORT_DRAW(ch)
#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)
#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 {{{ // Normal mode {{{
static inline void
handle_normal_mode_char(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback) {
HANDLER(text) { #define CALL_SCREEN_HANDLER(name) REPORT_COMMAND(#name, ch); name(screen); break;
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) { switch(ch) {
case BEL: case BEL:
CALL_SCREEN_HANDLER(screen_bell); CALL_SCREEN_HANDLER(screen_bell);
@ -117,466 +71,209 @@ HANDLER(text) {
case CR: case CR:
CALL_SCREEN_HANDLER(screen_carriage_return); CALL_SCREEN_HANDLER(screen_carriage_return);
case SO: case SO:
CALL_SCREEN_HANDLER(screen_shift_out); REPORT_ERROR("Unhandled charset change command (SO), ignoring"); break;
case SI: case SI:
CALL_SCREEN_HANDLER(screen_shift_in); REPORT_ERROR("Unhandled charset change command (SI), ignoring"); break;
case IND:
CALL_SCREEN_HANDLER(screen_index);
case RI:
CALL_SCREEN_HANDLER(screen_reverse_index);
case NEL:
CALL_SCREEN_HANDLER(screen_linefeed);
case HTS:
CALL_SCREEN_HANDLER(screen_set_tab_stop);
case ESC: case ESC:
DRAW_TEXT((*pos)-1); SET_STATE(ESC_STATE); return;
case CSI: case CSI:
DRAW_TEXT((*pos)-1); SET_STATE(CSI_STATE); return;
case OSC: case OSC:
DRAW_TEXT((*pos)-1); SET_STATE(OSC_STATE); return; case DCS:
SET_STATE(ch); break;
case NUL: case NUL:
case DEL: case DEL:
break; // no-op break; // no-op
default: default:
if (!screen->parser_has_pending_text) { REPORT_DRAW(ch);
screen->parser_has_pending_text = true; screen_draw(screen, ch);
screen->parser_text_start = (*pos) - 1; break;
} }
} #undef CALL_SCREEN_HANDLER
} } // }}}
DRAW_TEXT(*pos);
}
#undef DRAW_TEXT
// }}}
// Parse ESC {{{ // Esc mode {{{
static inline void
static inline void screen_linefeed2(Screen *screen) { screen_linefeed(screen, '\n'); } 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;
static inline void escape_dispatch(Screen *screen, uint8_t ch, PyObject UNUSED *dump_callback) { switch(screen->parser_buf_pos) {
#define CALL_ED(name) REPORT_COMMAND(name); name(screen); break; case 0:
switch (ch) { switch (ch) {
case RIS: 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); CALL_ED(screen_reset);
case IND: case ESC_IND:
CALL_ED(screen_index); CALL_ED(screen_index);
case NEL: case ESC_NEL:
CALL_ED(screen_linefeed2); CALL_ED(screen_linefeed);
case RI: case ESC_RI:
CALL_ED(screen_reverse_index); CALL_ED(screen_reverse_index);
case HTS: case ESC_HTS:
CALL_ED(screen_set_tab_stop); CALL_ED(screen_set_tab_stop);
case DECSC: case ESC_DECSC:
CALL_ED(screen_save_cursor); CALL_ED(screen_save_cursor);
case DECRC: case ESC_DECRC:
CALL_ED(screen_restore_cursor); CALL_ED(screen_restore_cursor);
case DECPNM: case ESC_DECPNM:
CALL_ED(screen_normal_keypad_mode); CALL_ED(screen_normal_keypad_mode);
case DECPAM: case ESC_DECPAM:
CALL_ED(screen_alternate_keypad_mode); 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: default:
REPORT_ERROR("%s%d", "Unknown char in escape_dispatch: ", ch); 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 #undef CALL_ED
} } // }}}
static inline void sharp_dispatch(Screen *screen, uint8_t ch, PyObject UNUSED *dump_callback) { // OSC mode {{{
switch(ch) { static inline void
case DECALN: dispatch_osc(Screen *screen) {
REPORT_COMMAND(screen_alignment_display); screen->parser_buf_pos++;
screen_alignment_display(screen);
break;
default:
REPORT_ERROR("%s%d", "Unknown char in sharp_dispatch: ", ch);
}
} }
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 {{{ // DCS mode {{{
static inline void
#define MAX_PARAMS 100 dispatch_dcs(Screen *screen) {
screen->parser_buf_pos++;
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;
} }
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) { static inline bool
unsigned int code = 0; accumulate_osc(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback) {
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;
}
switch(ch) { switch(ch) {
case ST: 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: case BEL:
if(!screen->parser_buf[0] && screen->parser_buf[1]) { return true;
// Only a numeric component case NUL:
screen->parser_buf[0] = screen->parser_buf_pos; case DEL:
}
HANDLE_OSC;
SET_STATE(NORMAL_STATE);
break; 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: 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) { if (screen->parser_buf_pos >= PARSER_BUF_SZ - 1) {
REPORT_ERROR("OSC control sequence too long, truncating"); REPORT_ERROR("OSC sequence too long, truncating.");
HANDLE_OSC; return true;
SET_STATE(NORMAL_STATE);
break;
} }
screen->parser_buf[screen->parser_buf_pos++] = ch; screen->parser_buf[screen->parser_buf_pos++] = ch;
break;
} }
} return false;
// }}}
// Parse DCS {{{
static void handle_dcs(Screen *screen, PyObject UNUSED *dump_callback) {
screen->parser_buf_pos = 0;
return;
} }
HANDLER(dcs) { static inline bool
// http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Device-Control-functions accumulate_dcs(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback) {
#ifndef DUMP_COMMANDS switch(ch) {
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) {
case ST: 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; break;
default: default:
if (screen->parser_buf_pos >= PARSER_BUF_SZ - 1) { REPORT_ERROR("DCS sequence contained non-printable character: 0x%x ignoring the sequence", ch);
DISPATCH_DCS; }
} else screen->parser_buf[screen->parser_buf_pos++] = 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; break;
} }
}
#undef HANDLE
} }
// }}} // }}}
static inline void _parse_bytes(Screen *screen, uint8_t *buf, Py_ssize_t len, PyObject UNUSED *dump_callback) { // Boilerplate {{{
unsigned int i = 0;
#ifdef DUMP_COMMANDS #ifdef DUMP_COMMANDS
#define CALL_HANDLER(name) read_##name(screen, buf, len, &i, dump_callback); break; #define FNAME(x) x##_dump
#else #else
#define CALL_HANDLER(name) read_##name(screen, buf, len, &i); break; #define FNAME(x) x
#endif #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* PyObject*
#ifdef DUMP_COMMANDS FNAME(parse_bytes)(PyObject UNUSED *self, PyObject *args) {
parse_bytes_dump(PyObject UNUSED *self, PyObject *args) {
#else
parse_bytes(PyObject UNUSED *self, PyObject *args) {
#endif
PyObject *dump_callback = NULL; PyObject *dump_callback = NULL;
Py_buffer pybuf; Py_buffer pybuf;
Screen *screen; Screen *screen;
#ifdef DUMP_COMMANDS #ifdef DUMP_COMMANDS
if (!PyArg_ParseTuple(args, "OO!y*", &dump_callback, &Screen_Type, &screen, &pybuf)) return NULL; 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 #else
if (!PyArg_ParseTuple(args, "O!y*", &Screen_Type, &screen, &pybuf)) return NULL; if (!PyArg_ParseTuple(args, "O!y*", &Screen_Type, &screen, &pybuf)) return NULL;
#endif #endif
@ -585,11 +282,7 @@ parse_bytes(PyObject UNUSED *self, PyObject *args) {
} }
PyObject* PyObject*
#ifdef DUMP_COMMANDS FNAME(read_bytes)(PyObject UNUSED *self, PyObject *args) {
read_bytes_dump(PyObject UNUSED *self, PyObject *args) {
#else
read_bytes(PyObject UNUSED *self, PyObject *args) {
#endif
PyObject *dump_callback = NULL; PyObject *dump_callback = NULL;
Py_ssize_t len; Py_ssize_t len;
Screen *screen; Screen *screen;
@ -613,4 +306,5 @@ read_bytes(PyObject UNUSED *self, PyObject *args) {
if(len > 0) { Py_RETURN_TRUE; } if(len > 0) { Py_RETURN_TRUE; }
Py_RETURN_FALSE; Py_RETURN_FALSE;
} }
#undef FNAME
// }}}

View File

@ -23,9 +23,6 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
self = (Screen *)type->tp_alloc(type, 0); self = (Screen *)type->tp_alloc(type, 0);
if (self != NULL) { 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->columns = columns; self->lines = lines;
self->modes = empty_modes; self->modes = empty_modes;
self->utf8_state = 0; self->utf8_state = 0;
@ -47,9 +44,6 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
void screen_reset(Screen *self) { void screen_reset(Screen *self) {
if (self->linebuf == self->alt_linebuf) screen_toggle_screen_buffer(self); if (self->linebuf == self->alt_linebuf) screen_toggle_screen_buffer(self);
linebuf_clear(self->linebuf); 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->modes = empty_modes;
self->utf8_state = 0; self->utf8_state = 0;
self->margin_top = 0; self->margin_bottom = self->lines - 1; self->margin_top = 0; self->margin_bottom = self->lines - 1;
@ -126,52 +120,22 @@ dealloc(Screen* self) {
// Draw text {{{ // Draw text {{{
void screen_shift_out(Screen *self, uint8_t UNUSED ch) { static inline unsigned int
self->current_charset = 1; safe_wcwidth(uint32_t ch) {
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) {
int ans = wcwidth(ch); int ans = wcwidth(ch);
if (ans < 0) ans = 1; if (ans < 0) ans = 1;
return MIN(2, ans); return MIN(2, ans);
} }
static inline void void
draw_codepoint(Screen UNUSED *self, uint32_t ch) { screen_draw(Screen *self, uint32_t ch) {
if (is_ignored_char(ch)) return; if (is_ignored_char(ch)) return;
unsigned int x = self->cursor->x, y = self->cursor->y;
unsigned int char_width = safe_wcwidth(ch); unsigned int char_width = safe_wcwidth(ch);
if (self->columns - self->cursor->x < char_width) { if (self->columns - self->cursor->x < char_width) {
if (self->modes.mDECAWM) { if (self->modes.mDECAWM) {
screen_carriage_return(self, 13); screen_carriage_return(self);
screen_linefeed(self, 10); screen_linefeed(self);
self->linebuf->continued_map[self->cursor->y] = true; self->linebuf->continued_map[self->cursor->y] = true;
} else { } else {
self->cursor->x = self->columns - char_width; 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); 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); if (x != self->cursor->x || y != self->cursor->y) tracker_cursor_changed(self->change_tracker);
} }
// }}} // }}}
// Graphics {{{ // Graphics {{{
@ -451,10 +381,10 @@ void screen_reset_mode(Screen *self, unsigned int mode) {
// Cursor {{{ // Cursor {{{
void screen_backspace(Screen *self, uint8_t UNUSED ch) { void screen_backspace(Screen *self) {
screen_cursor_back(self, 1, -1); 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. // 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++) {
@ -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) { if (self->cursor->x != 0) {
self->cursor->x = 0; self->cursor->x = 0;
tracker_cursor_changed(self->change_tracker); tracker_cursor_changed(self->change_tracker);
} }
} }
void screen_linefeed(Screen *self, uint8_t UNUSED ch) { void screen_linefeed(Screen *self) {
screen_index(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); 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; SavepointBuffer *pts = self->linebuf == self->main_linebuf ? &self->main_savepoints : &self->alt_savepoints;
Savepoint *sp = savepoints_push(pts); Savepoint *sp = savepoints_push(pts);
cursor_copy_to(self->cursor, &(sp->cursor)); 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->mDECOM = self->modes.mDECOM;
sp->mDECAWM = self->modes.mDECAWM; sp->mDECAWM = self->modes.mDECAWM;
sp->utf8_state = self->utf8_state; sp->utf8_state = self->utf8_state;
@ -601,13 +528,7 @@ void screen_restore_cursor(Screen *self) {
screen_cursor_position(self, 1, 1); screen_cursor_position(self, 1, 1);
tracker_cursor_changed(self->change_tracker); tracker_cursor_changed(self->change_tracker);
screen_reset_mode(self, DECOM); screen_reset_mode(self, DECOM);
self->current_charset = 2;
self->g0_charset = translation_table('B');
self->g1_charset = translation_table('0');
} else { } 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; self->utf8_state = sp->utf8_state;
if (sp->mDECOM) screen_set_mode(self, DECOM); if (sp->mDECOM) screen_set_mode(self, DECOM);
if (sp->mDECAWM) screen_set_mode(self, DECAWM); 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) { if (top <= self->cursor->y && self->cursor->y <= bottom) {
linebuf_insert_lines(self->linebuf, count, 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); 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) { if (top <= self->cursor->y && self->cursor->y <= bottom) {
linebuf_delete_lines(self->linebuf, count, 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); 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 {{{ // Device control {{{
void screen_bell(Screen UNUSED *self, uint8_t ch) { void screen_bell(Screen UNUSED *self) {
FILE *f = fopen("/dev/tty", "w"); FILE *f = fopen("/dev/tty", "w");
static const char *bell = "\007";
if (f != NULL) { if (f != NULL) {
fwrite(&ch, 1, 1, f); fwrite(bell, 1, 1, f);
fclose(f); fclose(f);
} }
} }
@ -901,11 +823,14 @@ line(Screen *self, PyObject *val) {
} }
static PyObject* static PyObject*
draw(Screen *self, PyObject *args) { draw(Screen *self, PyObject *src) {
#define draw_doc "" #define draw_doc ""
Py_buffer pybuf; if (!PyUnicode_Check(src)) { PyErr_SetString(PyExc_TypeError, "A unicode string is required"); return NULL; }
if(!PyArg_ParseTuple(args, "y*", &pybuf)) return NULL; if (PyUnicode_READY(src) != 0) { return PyErr_NoMemory(); }
screen_draw(self, pybuf.buf, pybuf.len); 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; Py_RETURN_NONE;
} }
@ -1097,7 +1022,7 @@ COUNT_WRAP(cursor_forward)
static PyMethodDef methods[] = { static PyMethodDef methods[] = {
METHOD(line, METH_O) METHOD(line, METH_O)
METHOD(draw, METH_VARARGS) METHOD(draw, METH_O)
METHOD(set_mode, METH_VARARGS) METHOD(set_mode, METH_VARARGS)
METHOD(reset_mode, METH_VARARGS) METHOD(reset_mode, METH_VARARGS)
METHOD(enable_focus_tracking, METH_NOARGS) METHOD(enable_focus_tracking, METH_NOARGS)
@ -1139,7 +1064,6 @@ static PyMemberDef members[] = {
{"columns", T_UINT, offsetof(Screen, columns), READONLY, "columns"}, {"columns", T_UINT, offsetof(Screen, columns), READONLY, "columns"},
{"margin_top", T_UINT, offsetof(Screen, margin_top), READONLY, "margin_top"}, {"margin_top", T_UINT, offsetof(Screen, margin_top), READONLY, "margin_top"},
{"margin_bottom", T_UINT, offsetof(Screen, margin_bottom), READONLY, "margin_bottom"}, {"margin_bottom", T_UINT, offsetof(Screen, margin_bottom), READONLY, "margin_bottom"},
{"current_charset", T_UINT, offsetof(Screen, current_charset), READONLY, "current_charset"},
{NULL} {NULL}
}; };

View File

@ -36,7 +36,7 @@ class Callbacks:
self.wtcbuf = self.iconbuf = self.titlebuf = self.colorbuf = b'' self.wtcbuf = self.iconbuf = self.titlebuf = self.colorbuf = b''
class TestScreen(BaseTest): class TestParser(BaseTest):
def parse_bytes_dump(self, s, x, *cmds): def parse_bytes_dump(self, s, x, *cmds):
cd = CmdDump() cd = CmdDump()