Use the main VT parser in pending mode as well

Should get much closer semantics in the two cases and its nice not to
have an extra mini VT parser for pending mode. There is a performance
hit in pending mode, since now the pending mode bytes are round tripped
via utf-8 decoding/encoding, but its worth it for the code
simplification.
This commit is contained in:
Kovid Goyal 2021-06-30 10:52:22 +05:30
parent d6b6d3f59f
commit e6a17f78b6
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 110 additions and 113 deletions

View File

@ -151,10 +151,10 @@ _report_params(PyObject *dump_callback, const char *name, int *params, unsigned
// }}}
// Normal mode {{{
static inline void
static void
screen_nel(Screen *screen) { screen_carriage_return(screen); screen_linefeed(screen); }
static inline void
static void
dispatch_normal_mode_char(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback) {
#define CALL_SCREEN_HANDLER(name) REPORT_COMMAND(name); name(screen); break;
switch(ch) {
@ -203,7 +203,7 @@ dispatch_normal_mode_char(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dum
} // }}}
// Esc mode {{{
static inline void
static void
dispatch_esc_mode_char(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback) {
#define CALL_ED(name) REPORT_COMMAND(name); name(screen); SET_STATE(0);
#define CALL_ED1(name, ch) REPORT_COMMAND(name, ch); name(screen, ch); SET_STATE(0);
@ -1011,7 +1011,7 @@ dispatch_dcs(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
screen->pending_mode.activated_at = monotonic();
REPORT_COMMAND(screen_start_pending_mode);
} else {
// ignore stop without matching start, see _queue_pending_bytes()
// ignore stop without matching start, see queue_pending_bytes()
// for how stop is detected while in pending mode.
REPORT_ERROR("Pending mode stop command issued while not in pending mode");
REPORT_COMMAND(screen_stop_pending_mode);
@ -1306,110 +1306,107 @@ FLUSH_DRAW;
return i;
}
static size_t
_queue_pending_bytes(Screen *screen, const uint8_t *buf, size_t len, PyObject *dump_callback DUMP_UNUSED) {
size_t pos = 0;
enum STATE { NORMAL, MAYBE_DCS_OR_CSI, IN_DCS, IN_CSI, EXPECTING_DATA, EXPECTING_CSI_DATA, EXPECTING_SLASH };
#define pm screen->pending_mode
enum STATE state = pm.state;
#define stop_pending_mode \
if (state == EXPECTING_CSI_DATA) { REPORT_COMMAND(screen_reset_mode, 2026, 1); } \
else { REPORT_COMMAND(screen_stop_pending_mode); } \
pm.activated_at = 0; \
goto end;
#define start_pending_mode \
if (state == EXPECTING_CSI_DATA) { REPORT_COMMAND(screen_set_mode, 2026, 1); } \
else { REPORT_COMMAND(screen_start_pending_mode); } \
pm.activated_at = monotonic();
#define COPY(what) pm.buf[pm.used++] = what
#define COPY_STOP_BUF(for_dcs) { \
COPY(0x1b); \
if (for_dcs) { COPY('P'); COPY(PENDING_MODE_CHAR); } \
else { COPY('['); COPY('?'); } \
for (size_t i = 0; i < pm.stop_buf_pos; i++) { \
COPY(pm.stop_buf[i]); \
} \
pm.stop_buf_pos = 0;}
static void
write_pending_char(Screen *screen, uint32_t ch) {
screen->pending_mode.used += encode_utf8(ch, (char*)screen->pending_mode.buf + screen->pending_mode.used);
}
while (pos < len) {
uint8_t ch = buf[pos++];
switch(state) {
case NORMAL:
if (ch == ESC) state = MAYBE_DCS_OR_CSI;
else COPY(ch);
break;
case MAYBE_DCS_OR_CSI:
if (ch == 'P') state = IN_DCS;
else if (ch == '[') state = IN_CSI;
else {
state = NORMAL;
COPY(0x1b); COPY(ch);
static void
pending_normal_mode_char(Screen *screen, uint32_t ch, PyObject *dump_callback UNUSED) {
switch(ch) {
case ESC: case CSI: case OSC: case DCS: case APC: case PM:
SET_STATE(ch); break;
default:
write_pending_char(screen, ch); break;
}
break;
case IN_DCS:
if (ch == PENDING_MODE_CHAR) { state = EXPECTING_DATA; pm.stop_buf_pos = 0; }
else {
state = NORMAL;
COPY(0x1b); COPY('P'); COPY(ch);
}
static void
pending_esc_mode_char(Screen *screen, uint32_t ch, PyObject *dump_callback UNUSED) {
if (screen->parser_buf_pos > 0) {
write_pending_char(screen, ESC);
write_pending_char(screen, screen->parser_buf[screen->parser_buf_pos]);
write_pending_char(screen, ch);
SET_STATE(0);
return;
}
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_APC:
SET_STATE(APC); break;
case ESC_PM:
SET_STATE(PM); break;
case '%':
case '(':
case ')':
case '*':
case '+':
case '-':
case '.':
case '/':
case ' ':
case '#':
screen->parser_buf[screen->parser_buf_pos++] = ch;
break;
case IN_CSI:
if (ch == '?') { state = EXPECTING_CSI_DATA; pm.stop_buf_pos = 0; }
else {
state = NORMAL;
COPY(0x1b); COPY('['); COPY(ch);
default:
write_pending_char(screen, ESC); write_pending_char(screen, ch);
SET_STATE(0); break;
}
break;
case EXPECTING_CSI_DATA:
if (ch == 'h' || ch == 'l') {
if (pm.stop_buf_pos == 4 && memcmp(pm.stop_buf, "2026", 4) == 0) {
if (ch == 'h') { start_pending_mode } else { stop_pending_mode }
}
#define pb(i) screen->parser_buf[i]
static void
pending_escape_code(Screen *screen, char_type start_ch, char_type end_ch) {
write_pending_char(screen, start_ch);
for (unsigned i = 0; i < screen->parser_buf_pos; i++) write_pending_char(screen, pb(i));
write_pending_char(screen, end_ch);
}
static void pending_pm(Screen *screen, PyObject *dump_callback UNUSED) { pending_escape_code(screen, PM, ST); }
static void pending_apc(Screen *screen, PyObject *dump_callback UNUSED) { pending_escape_code(screen, APC, ST); }
static void pending_osc(Screen *screen, PyObject *dump_callback UNUSED) { pending_escape_code(screen, OSC, ST); }
static void
pending_dcs(Screen *screen, PyObject *dump_callback DUMP_UNUSED) {
if (screen->parser_buf_pos >= 3 && pb(0) == '=' && (pb(1) == '1' || pb(1) == '2') && pb(2) == 's') {
screen->pending_mode.activated_at = pb(1) == '1' ? monotonic() : 0;
if (pb(1) == '1') {
REPORT_COMMAND(screen_start_pending_mode);
screen->pending_mode.activated_at = monotonic();
} else {
COPY_STOP_BUF(false); COPY(ch);
REPORT_COMMAND(screen_stop_pending_mode);
screen->pending_mode.activated_at = 0;
}
state = NORMAL;
} else pending_escape_code(screen, DCS, ST);
}
static void
pending_csi(Screen *screen, PyObject *dump_callback DUMP_UNUSED) {
if (screen->parser_buf_pos == 5 && pb(0) == '?' && pb(1) == '2' && pb(2) == '0' && pb(3) == '2' && pb(4) == '6' && (pb(5) == 'h' || pb(5) == 'l')) {
if (pb(5) == 'h') {
REPORT_COMMAND(screen_set_mode, 2026, 1);
screen->pending_mode.activated_at = monotonic();
} else {
pm.stop_buf[pm.stop_buf_pos++] = ch;
if (pm.stop_buf_pos >= sizeof(pm.stop_buf)) {
state = NORMAL;
COPY_STOP_BUF(false);
}
}
break;
case EXPECTING_DATA:
pm.stop_buf[pm.stop_buf_pos++] = ch;
if (ch == 0x1b) state = EXPECTING_SLASH;
else {
if (pm.stop_buf_pos >= sizeof(pm.stop_buf)) {
state = NORMAL;
COPY_STOP_BUF(true);
}
}
break;
case EXPECTING_SLASH:
if (
ch == '\\' &&
pm.stop_buf_pos >= 2 &&
(pm.stop_buf[0] == '1' || pm.stop_buf[0] == '2') &&
pm.stop_buf[1] == 's'
) {
// We found a pending mode sequence
if (pm.stop_buf[0] == '2') { stop_pending_mode } else { start_pending_mode }
} else {
COPY_STOP_BUF(true); COPY(ch);
}
state = NORMAL;
break;
}
REPORT_COMMAND(screen_reset_mode, 2026, 1);
screen->pending_mode.activated_at = 0;
}
} else pending_escape_code(screen, CSI, pb(screen->parser_buf_pos));
}
#undef pb
static size_t
queue_pending_bytes(Screen *screen, const uint8_t *buf, size_t len, PyObject *dump_callback DUMP_UNUSED) {
unsigned int i;
decode_loop(pending, if (!screen->pending_mode.activated_at) goto end);
end:
pm.state = state;
return pos;
#undef COPY
#undef COPY_STOP_BUF
#undef stop_pending_mode
#undef start_pending_mode
#undef pm
return i;
}
static inline void
@ -1433,30 +1430,31 @@ do_parse_bytes(Screen *screen, const uint8_t *read_buf, const size_t read_buf_sz
case PARSE_PENDING:
_parse_bytes(screen, screen->pending_mode.buf, screen->pending_mode.used, dump_callback);
screen->pending_mode.used = 0; screen->pending_mode.state = 0;
screen->pending_mode.used = 0;
screen->pending_mode.activated_at = 0; // ignore any pending starts in the pending bytes
state = START;
break;
case PARSE_READ_BUF:
screen->pending_mode.activated_at = 0; screen->pending_mode.state = 0;
screen->pending_mode.activated_at = 0;
read_buf_pos += _parse_bytes_watching_for_pending(screen, read_buf + read_buf_pos, read_buf_sz - read_buf_pos, dump_callback);
state = START;
break;
case QUEUE_PENDING: {
if (screen->pending_mode.capacity - screen->pending_mode.used < read_buf_sz + sizeof(screen->pending_mode.stop_buf)) {
const size_t needed_space = read_buf_sz * 2;
if (screen->pending_mode.capacity - screen->pending_mode.used < needed_space) {
if (screen->pending_mode.capacity >= READ_BUF_SZ) {
// Too much pending data, drain it
screen->pending_mode.activated_at = 0;
state = START;
break;
}
screen->pending_mode.capacity = MAX(screen->pending_mode.capacity * 2, screen->pending_mode.used + read_buf_sz + sizeof(screen->pending_mode.stop_buf));
screen->pending_mode.capacity = MAX(screen->pending_mode.capacity * 2, screen->pending_mode.used + needed_space);
screen->pending_mode.buf = realloc(screen->pending_mode.buf, screen->pending_mode.capacity);
if (!screen->pending_mode.buf) fatal("Out of memory");
}
read_buf_pos += _queue_pending_bytes(screen, read_buf + read_buf_pos, read_buf_sz - read_buf_pos, dump_callback);
read_buf_pos += queue_pending_bytes(screen, read_buf + read_buf_pos, read_buf_sz - read_buf_pos, dump_callback);
state = START;
} break;
}

View File

@ -116,11 +116,9 @@ typedef struct {
CursorRenderInfo cursor_render_info;
struct {
size_t capacity, used, stop_buf_pos;
size_t capacity, used;
uint8_t *buf;
monotonic_t activated_at, wait_time;
int state;
uint8_t stop_buf[32];
} pending_mode;
DisableLigature disable_ligatures;
PyObject *marker;

View File

@ -363,6 +363,7 @@ class TestParser(BaseTest):
pb('\033P=1sxyz;.;\033\\''\033P=2skjf".,><?_+)98\033\\', ('screen_start_pending_mode',), ('screen_stop_pending_mode',))
pb('\033P=1s\033\\f\033P=1s\033\\', ('screen_start_pending_mode',), ('screen_start_pending_mode',))
pb('\033P=2s\033\\', ('screen_stop_pending_mode',), ('draw', 'f'))
pb('\033P=1s\033\\XXX\033P=2s\033\\', ('screen_start_pending_mode',), ('screen_stop_pending_mode',), ('draw', 'XXX'))
pb('\033[?2026hXXX\033[?2026l', ('screen_set_mode', 2026, 1), ('screen_reset_mode', 2026, 1), ('draw', 'XXX'))
pb('\033[?2026h\033[32ma\033[?2026l', ('screen_set_mode', 2026, 1), ('screen_reset_mode', 2026, 1), ('select_graphic_rendition', '32 '), ('draw', 'a'))