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.
#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.
// ---------------------

View File

@ -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)

View File

@ -1,35 +1,13 @@
/*
* parser.c
* Copyright (C) 2016 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#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
// }}}

View File

@ -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}
};

View File

@ -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()