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 {{{ // Normal mode {{{
static inline void static void
screen_nel(Screen *screen) { screen_carriage_return(screen); screen_linefeed(screen); } 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) { 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; #define CALL_SCREEN_HANDLER(name) REPORT_COMMAND(name); name(screen); break;
switch(ch) { switch(ch) {
@ -203,7 +203,7 @@ dispatch_normal_mode_char(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dum
} // }}} } // }}}
// Esc mode {{{ // Esc mode {{{
static inline void static void
dispatch_esc_mode_char(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback) { 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_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); #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(); screen->pending_mode.activated_at = monotonic();
REPORT_COMMAND(screen_start_pending_mode); REPORT_COMMAND(screen_start_pending_mode);
} else { } 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. // for how stop is detected while in pending mode.
REPORT_ERROR("Pending mode stop command issued while not in pending mode"); REPORT_ERROR("Pending mode stop command issued while not in pending mode");
REPORT_COMMAND(screen_stop_pending_mode); REPORT_COMMAND(screen_stop_pending_mode);
@ -1306,110 +1306,107 @@ FLUSH_DRAW;
return i; return i;
} }
static size_t static void
_queue_pending_bytes(Screen *screen, const uint8_t *buf, size_t len, PyObject *dump_callback DUMP_UNUSED) { write_pending_char(Screen *screen, uint32_t ch) {
size_t pos = 0; screen->pending_mode.used += encode_utf8(ch, (char*)screen->pending_mode.buf + screen->pending_mode.used);
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;}
while (pos < len) { static void
uint8_t ch = buf[pos++]; pending_normal_mode_char(Screen *screen, uint32_t ch, PyObject *dump_callback UNUSED) {
switch(state) { switch(ch) {
case NORMAL: case ESC: case CSI: case OSC: case DCS: case APC: case PM:
if (ch == ESC) state = MAYBE_DCS_OR_CSI; SET_STATE(ch); break;
else COPY(ch); default:
break; write_pending_char(screen, 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);
}
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);
}
break;
case IN_CSI:
if (ch == '?') { state = EXPECTING_CSI_DATA; pm.stop_buf_pos = 0; }
else {
state = NORMAL;
COPY(0x1b); COPY('['); COPY(ch);
}
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 }
} else {
COPY_STOP_BUF(false); COPY(ch);
}
state = NORMAL;
} 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;
}
} }
}
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;
default:
write_pending_char(screen, ESC); write_pending_char(screen, ch);
SET_STATE(0); break;
}
}
#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 {
REPORT_COMMAND(screen_stop_pending_mode);
screen->pending_mode.activated_at = 0;
}
} 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 {
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: end:
pm.state = state; return i;
return pos;
#undef COPY
#undef COPY_STOP_BUF
#undef stop_pending_mode
#undef start_pending_mode
#undef pm
} }
static inline void 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: case PARSE_PENDING:
_parse_bytes(screen, screen->pending_mode.buf, screen->pending_mode.used, dump_callback); _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 screen->pending_mode.activated_at = 0; // ignore any pending starts in the pending bytes
state = START; state = START;
break; break;
case PARSE_READ_BUF: 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); read_buf_pos += _parse_bytes_watching_for_pending(screen, read_buf + read_buf_pos, read_buf_sz - read_buf_pos, dump_callback);
state = START; state = START;
break; break;
case QUEUE_PENDING: { 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) { if (screen->pending_mode.capacity >= READ_BUF_SZ) {
// Too much pending data, drain it // Too much pending data, drain it
screen->pending_mode.activated_at = 0; screen->pending_mode.activated_at = 0;
state = START; state = START;
break; 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); screen->pending_mode.buf = realloc(screen->pending_mode.buf, screen->pending_mode.capacity);
if (!screen->pending_mode.buf) fatal("Out of memory"); 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; state = START;
} break; } break;
} }

View File

@ -116,11 +116,9 @@ typedef struct {
CursorRenderInfo cursor_render_info; CursorRenderInfo cursor_render_info;
struct { struct {
size_t capacity, used, stop_buf_pos; size_t capacity, used;
uint8_t *buf; uint8_t *buf;
monotonic_t activated_at, wait_time; monotonic_t activated_at, wait_time;
int state;
uint8_t stop_buf[32];
} pending_mode; } pending_mode;
DisableLigature disable_ligatures; DisableLigature disable_ligatures;
PyObject *marker; 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=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=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=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[?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')) pb('\033[?2026h\033[32ma\033[?2026l', ('screen_set_mode', 2026, 1), ('screen_reset_mode', 2026, 1), ('select_graphic_rendition', '32 '), ('draw', 'a'))