Go back to a single code path for drawing text

Slightly reduces pure ASCII performance and improves Unicode
performance. We should be able to get pure ASCII performance back
via SIMD eventually.
This commit is contained in:
Kovid Goyal 2023-11-16 12:32:34 +05:30
parent b41cf52ce4
commit 718f4b328f
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
8 changed files with 270 additions and 230 deletions

View file

@ -124,6 +124,13 @@ dealloc(LineBuf* self) {
Py_TYPE(self)->tp_free((PyObject*)self);
}
void
linebuf_init_cells(LineBuf *lb, index_type idx, CPUCell **c, GPUCell **g) {
const index_type ynum = lb->line_map[idx];
*c = cpu_lineptr(lb, ynum);
*g = gpu_lineptr(lb, ynum);
}
static void
init_line(LineBuf *lb, Line *l, index_type ynum) {
l->cpu_cells = cpu_lineptr(lb, ynum);

View file

@ -433,10 +433,10 @@ width(Line *self, PyObject *val) {
}
void
line_add_combining_char(Line *self, uint32_t ch, unsigned int x) {
CPUCell *cell = self->cpu_cells + x;
line_add_combining_char(CPUCell *cpu_cells, GPUCell *gpu_cells, uint32_t ch, unsigned int x) {
CPUCell *cell = cpu_cells + x;
if (!cell->ch) {
if (x > 0 && (self->gpu_cells[x-1].attrs.width) == 2 && self->cpu_cells[x-1].ch) cell = self->cpu_cells + x - 1;
if (x > 0 && (gpu_cells[x-1].attrs.width) == 2 && cpu_cells[x-1].ch) cell = cpu_cells + x - 1;
else return; // don't allow adding combining chars to a null cell
}
for (unsigned i = 0; i < arraysz(cell->cc_idx); i++) {
@ -455,7 +455,7 @@ add_combining_char(Line* self, PyObject *args) {
PyErr_SetString(PyExc_ValueError, "Column index out of bounds");
return NULL;
}
line_add_combining_char(self, new_char, x);
line_add_combining_char(self->cpu_cells, self->gpu_cells, new_char, x);
Py_RETURN_NONE;
}

View file

@ -87,7 +87,7 @@ void line_apply_cursor(Line *self, Cursor *cursor, unsigned int at, unsigned int
char_type line_get_char(Line *self, index_type at);
void line_set_char(Line *, unsigned int , uint32_t , unsigned int , Cursor *, hyperlink_id_type);
void line_right_shift(Line *, unsigned int , unsigned int );
void line_add_combining_char(Line *, uint32_t , unsigned int );
void line_add_combining_char(CPUCell *, GPUCell *, uint32_t , unsigned int );
index_type line_url_start_at(Line *self, index_type x);
index_type line_url_end_at(Line *self, index_type x, bool, char_type, bool);
bool line_startswith_url_chars(Line*);
@ -101,6 +101,7 @@ PyObject* unicode_in_range(const Line *self, const index_type start, const index
PyObject* line_as_unicode(Line *, bool);
void linebuf_init_line(LineBuf *, index_type);
void linebuf_init_cells(LineBuf *lb, index_type ynum, CPUCell **c, GPUCell **g);
void linebuf_clear(LineBuf *, char_type ch);
void linebuf_index(LineBuf* self, index_type top, index_type bottom);
void linebuf_reverse_index(LineBuf *self, index_type top, index_type bottom);

View file

@ -25,6 +25,7 @@
#include "modes.h"
#include "wcwidth-std.h"
#include "wcswidth.h"
#include <stdalign.h>
#include "keys.h"
#include "vt-parser.h"
@ -479,30 +480,16 @@ dealloc(Screen* self) {
} // }}}
// Draw text {{{
typedef struct text_loop_state {
bool image_placeholder_marked;
CPUCell *cp; GPUCell *gp;
} text_loop_state;
static void
move_widened_char(Screen *self, CPUCell* cpu_cell, GPUCell *gpu_cell, index_type xpos, index_type ypos) {
self->cursor->x = xpos; self->cursor->y = ypos;
CPUCell src_cpu = *cpu_cell, *dest_cpu;
GPUCell src_gpu = *gpu_cell, *dest_gpu;
line_clear_text(self->linebuf->line, xpos, 1, BLANK_CHAR);
if (self->modes.mDECAWM) { // overflow goes onto next line
linebuf_set_last_char_as_continuation(self->linebuf, self->cursor->y, true);
screen_carriage_return(self);
screen_linefeed(self);
linebuf_init_line(self->linebuf, self->cursor->y);
dest_cpu = self->linebuf->line->cpu_cells;
dest_gpu = self->linebuf->line->gpu_cells;
self->cursor->x = MIN(2u, self->columns);
linebuf_mark_line_dirty(self->linebuf, self->cursor->y);
} else {
dest_cpu = cpu_cell - 1;
dest_gpu = gpu_cell - 1;
self->cursor->x = self->columns;
}
*dest_cpu = src_cpu;
*dest_gpu = src_gpu;
continue_to_next_line(Screen *self) {
linebuf_set_last_char_as_continuation(self->linebuf, self->cursor->y, true);
self->cursor->x = 0;
screen_linefeed(self);
}
static bool
@ -520,6 +507,42 @@ selection_has_screen_line(const Selections *selections, const int y) {
return false;
}
static void
init_text_loop_line(Screen *self, text_loop_state *s) {
if (self->modes.mIRM) {
linebuf_init_line(self->linebuf, self->cursor->y);
s->cp = self->linebuf->line->cpu_cells; s->gp = self->linebuf->line->gpu_cells;
} else linebuf_init_cells(self->linebuf, self->cursor->y, &s->cp, &s->gp);
if (selection_has_screen_line(&self->selections, self->cursor->y)) clear_selection(&self->selections);
linebuf_mark_line_dirty(self->linebuf, self->cursor->y);
s->image_placeholder_marked = false;
}
static void
move_widened_char(Screen *self, text_loop_state *s, CPUCell* cpu_cell, GPUCell *gpu_cell, index_type xpos, index_type ypos, const CPUCell cc, const GPUCell g) {
self->cursor->x = xpos; self->cursor->y = ypos;
CPUCell src_cpu = *cpu_cell, *dest_cpu;
GPUCell src_gpu = *gpu_cell, *dest_gpu;
memcpy(cpu_cell, &cc, sizeof(cc));
memcpy(gpu_cell, &g, sizeof(g));
if (self->modes.mDECAWM) { // overflow goes onto next line
continue_to_next_line(self);
init_text_loop_line(self, s);
dest_cpu = s->cp; dest_gpu = s->gp;
self->cursor->x = MIN(2u, self->columns);
} else {
dest_cpu = cpu_cell - 1;
dest_gpu = gpu_cell - 1;
self->cursor->x = self->columns;
}
*dest_cpu = src_cpu; *dest_gpu = src_gpu;
memcpy(dest_cpu + 1 , &cc, sizeof(cc));
memcpy(dest_gpu + 1, &g, sizeof(g));
dest_gpu[1].attrs.width = 0;
}
void
set_active_hyperlink(Screen *self, char *id, char *url) {
if (OPT(allow_hyperlinks)) {
@ -570,68 +593,52 @@ draw_second_flag_codepoint(Screen *self, char_type ch) {
xpos = self->columns - 2;
} else return false;
linebuf_init_line(self->linebuf, ypos);
CPUCell *cell = self->linebuf->line->cpu_cells + xpos;
CPUCell *cp; GPUCell *gp;
linebuf_init_cells(self->linebuf, ypos, &cp, &gp);
CPUCell *cell = cp + xpos;
if (!is_flag_pair(cell->ch, ch) || cell->cc_idx[0]) return false;
line_add_combining_char(self->linebuf->line, ch, xpos);
self->is_dirty = true;
if (selection_has_screen_line(&self->selections, ypos)) clear_selection(&self->selections);
linebuf_mark_line_dirty(self->linebuf, ypos);
line_add_combining_char(cp, gp, ch, xpos);
return true;
}
static void
draw_combining_char(Screen *self, char_type ch) {
draw_combining_char(Screen *self, text_loop_state *s, char_type ch, const CPUCell cc, const GPUCell g) {
bool has_prev_char = false;
index_type xpos = 0, ypos = 0;
if (self->cursor->x > 0) {
ypos = self->cursor->y;
linebuf_init_line(self->linebuf, ypos);
xpos = self->cursor->x - 1;
has_prev_char = true;
} else if (self->cursor->y > 0) {
ypos = self->cursor->y - 1;
linebuf_init_line(self->linebuf, ypos);
xpos = self->columns - 1;
has_prev_char = true;
}
if (self->cursor->x > 0) {
ypos = self->cursor->y;
linebuf_init_line(self->linebuf, ypos);
xpos = self->cursor->x - 1;
has_prev_char = true;
} else if (self->cursor->y > 0) {
ypos = self->cursor->y - 1;
linebuf_init_line(self->linebuf, ypos);
xpos = self->columns - 1;
has_prev_char = true;
}
if (has_prev_char) {
line_add_combining_char(self->linebuf->line, ch, xpos);
self->is_dirty = true;
if (selection_has_screen_line(&self->selections, ypos)) clear_selection(&self->selections);
linebuf_mark_line_dirty(self->linebuf, ypos);
CPUCell *cp; GPUCell *gp;
linebuf_init_cells(self->linebuf, ypos, &cp, &gp);
line_add_combining_char(cp, gp, ch, xpos);
if (ch == 0xfe0f) { // emoji presentation variation marker makes default text presentation emoji (narrow emoji) into wide emoji
CPUCell *cpu_cell = self->linebuf->line->cpu_cells + xpos;
GPUCell *gpu_cell = self->linebuf->line->gpu_cells + xpos;
CPUCell *cpu_cell = cp + xpos;
GPUCell *gpu_cell = gp + xpos;
if (gpu_cell->attrs.width != 2 && cpu_cell->cc_idx[0] == VS16 && is_emoji_presentation_base(cpu_cell->ch)) {
if (self->cursor->x <= self->columns - 1) line_set_char(self->linebuf->line, self->cursor->x, 0, 0, self->cursor, self->active_hyperlink_id);
gpu_cell->attrs.width = 2;
if (xpos == self->columns - 1) move_widened_char(self, cpu_cell, gpu_cell, xpos, ypos);
else self->cursor->x++;
if (xpos + 1 < self->columns) {
memcpy(cp + xpos + 1, &cc, sizeof(cc));
memcpy(gp + xpos + 1, &g, sizeof(g));
gp[xpos + 1].attrs.width = 0;
self->cursor->x++;
} else move_widened_char(self, s, cpu_cell, gpu_cell, xpos, ypos, cc, g);
}
} else if (ch == 0xfe0e) {
CPUCell *cpu_cell = self->linebuf->line->cpu_cells + xpos;
GPUCell *gpu_cell = self->linebuf->line->gpu_cells + xpos;
CPUCell *cpu_cell = cp + xpos;
GPUCell *gpu_cell = gp + xpos;
if (gpu_cell->attrs.width == 0 && cpu_cell->ch == 0 && xpos > 0) {
xpos--;
if (self->cursor->x > 0) self->cursor->x--;
cpu_cell = self->linebuf->line->cpu_cells + xpos;
gpu_cell = self->linebuf->line->gpu_cells + xpos;
cpu_cell--; gpu_cell--;
}
if (gpu_cell->attrs.width == 2 && cpu_cell->cc_idx[0] == VS15 && is_emoji_presentation_base(cpu_cell->ch)) {
gpu_cell->attrs.width = 1;
self->cursor->x--;
}
}
}
@ -650,15 +657,75 @@ screen_on_input(Screen *self) {
}
static void
screen_continue_to_next_line(Screen *self) {
linebuf_set_last_char_as_continuation(self->linebuf, self->cursor->y, true);
self->cursor->x = 0;
screen_linefeed(self);
ensure_cursor_not_on_wide_char_trailer_for_insert(Screen *self) {
if (self->cursor->x == 0) return;
CPUCell *c; GPUCell *g;
linebuf_init_cells(self->linebuf, self->cursor->y, &c, &g);
if (g[self->cursor->x - 1].attrs.width == 2) {
g[self->cursor->x-1].attrs.width = 1;
c[self->cursor->x-1].ch = ' ';
c[self->cursor->x].ch = 0;
memset(c[self->cursor->x-1].cc_idx, 0, sizeof(c[0].cc_idx));
memset(c[self->cursor->x].cc_idx, 0, sizeof(c[0].cc_idx));
}
}
void
screen_draw_printable_ascii(Screen *self, ByteLoader *it) {
screen_on_input(self);
static void
draw_text_loop(Screen *self, const uint32_t *chars, size_t num_chars, const CPUCell cc, const GPUCell g) {
text_loop_state s;
init_text_loop_line(self, &s);
for (size_t i = 0; i < num_chars; i++) {
uint32_t ch = chars[i];
if (ch < ' ') continue;
int char_width = 1;
if (ch > 0x7f) { // not printable ASCII
if (is_ignored_char(ch)) continue;
if (UNLIKELY(is_combining_char(ch))) {
if (UNLIKELY(is_flag_codepoint(ch))) {
if (draw_second_flag_codepoint(self, ch)) continue;
} else {
draw_combining_char(self, &s, ch, cc, g);
continue;
}
}
char_width = wcwidth_std(ch);
if (UNLIKELY(char_width < 1)) {
if (char_width == 0) continue;
char_width = 1;
}
}
self->last_graphic_char = ch;
if (UNLIKELY(self->columns < self->cursor->x + (unsigned int)char_width)) {
if (self->modes.mDECAWM) {
continue_to_next_line(self);
init_text_loop_line(self, &s);
} else {
self->cursor->x = self->columns - char_width;
ensure_cursor_not_on_wide_char_trailer_for_insert(self);
}
}
if (self->modes.mIRM) line_right_shift(self->linebuf->line, self->cursor->x, char_width);
if (UNLIKELY(!s.image_placeholder_marked && ch == IMAGE_PLACEHOLDER_CHAR)) {
linebuf_set_line_has_image_placeholders(self->linebuf, self->cursor->y, true);
s.image_placeholder_marked = true;
}
memcpy(s.gp + self->cursor->x, &g, sizeof(g));
memcpy(s.cp + self->cursor->x, &cc, sizeof(cc));
s.cp[self->cursor->x].ch = ch;
self->cursor->x++;
if (char_width == 2) {
s.gp[self->cursor->x-1].attrs.width = 2;
memcpy(s.gp + self->cursor->x, &g, sizeof(g));
memcpy(s.cp + self->cursor->x, &cc, sizeof(cc));
s.gp[self->cursor->x].attrs.width = 0;
self->cursor->x++;
}
}
#undef init_line
}
static void
draw_text(Screen *self, const uint32_t *chars, size_t num_chars) {
self->is_dirty = true;
const CPUCell cc = {.hyperlink_id=self->active_hyperlink_id};
GPUCell g = {.attrs=cursor_to_attrs(self->cursor, 1), .fg=self->cursor->fg & COL_MASK, .bg=self->cursor->bg & COL_MASK, .decoration_fg=self->cursor->decoration_fg & COL_MASK};
@ -666,86 +733,22 @@ screen_draw_printable_ascii(Screen *self, ByteLoader *it) {
g.decoration_fg = ((OPT(url_color) & COL_MASK) << 8) | 2;
g.attrs.decoration = OPT(url_style);
}
#define fill_single_line(num) { \
linebuf_init_line(self->linebuf, self->cursor->y); \
if (self->modes.mIRM) line_right_shift(self->linebuf->line, self->cursor->x, num); \
const unsigned limit = self->cursor->x + num; \
GPUCell *gp = self->linebuf->line->gpu_cells; \
CPUCell *cp = self->linebuf->line->cpu_cells; \
for (; self->cursor->x < limit; self->cursor->x++) { \
memcpy(gp + self->cursor->x, &g, sizeof(g)); \
memcpy(cp + self->cursor->x, &cc, sizeof(cc)); \
cp[self->cursor->x].ch = byte_loader_next(it); \
} \
if (selection_has_screen_line(&self->selections, self->cursor->y)) clear_selection(&self->selections); \
linebuf_mark_line_dirty(self->linebuf, self->cursor->y); \
ensure_cursor_not_on_wide_char_trailer_for_insert(self);
draw_text_loop(self, chars, num_chars, cc, g);
}
int avail = self->columns - self->cursor->x;
if (avail >= (int)it->num_left) {
fill_single_line(it->num_left);
} else {
if (self->modes.mDECAWM) {
while (it->num_left) {
avail = self->columns - self->cursor->x;
if (!avail) { screen_continue_to_next_line(self); avail = self->columns; }
unsigned nc = MIN((unsigned)avail, it->num_left);
fill_single_line(nc);
}
} else {
if (avail > 1) { fill_single_line(avail - 1); }
else if (avail == 0) self->cursor->x--;
fill_single_line(1);
}
}
self->last_graphic_char = self->linebuf->line->cpu_cells[self->cursor->x-1].ch;
void
screen_draw_text(Screen *self, const uint32_t *chars, size_t num_chars) {
screen_on_input(self);
draw_text(self, chars, num_chars);
}
static void
draw_codepoint(Screen *self, char_type ch, bool from_input_stream) {
if (from_input_stream) screen_on_input(self);
if (is_ignored_char(ch)) return;
if (UNLIKELY(is_combining_char(ch))) {
if (UNLIKELY(is_flag_codepoint(ch))) {
if (draw_second_flag_codepoint(self, ch)) return;
} else {
draw_combining_char(self, ch);
return;
}
}
int char_width = wcwidth_std(ch);
if (UNLIKELY(char_width < 1)) {
if (char_width == 0) return;
char_width = 1;
}
if (from_input_stream) self->last_graphic_char = ch;
if (UNLIKELY(self->columns - self->cursor->x < (unsigned int)char_width)) {
if (self->modes.mDECAWM) screen_continue_to_next_line(self);
else self->cursor->x = self->columns - char_width;
}
linebuf_init_line(self->linebuf, self->cursor->y);
if (self->modes.mIRM) {
line_right_shift(self->linebuf->line, self->cursor->x, char_width);
}
line_set_char(self->linebuf->line, self->cursor->x, ch, char_width, self->cursor, self->active_hyperlink_id);
self->cursor->x++;
if (char_width == 2) {
line_set_char(self->linebuf->line, self->cursor->x, 0, 0, self->cursor, self->active_hyperlink_id);
self->cursor->x++;
}
if (UNLIKELY(ch == IMAGE_PLACEHOLDER_CHAR)) {
linebuf_set_line_has_image_placeholders(self->linebuf, self->cursor->y, true);
}
self->is_dirty = true;
if (selection_has_screen_line(&self->selections, self->cursor->y)) clear_selection(&self->selections);
linebuf_mark_line_dirty(self->linebuf, self->cursor->y);
}
void
screen_draw(Screen *self, uint32_t och) {
draw_codepoint(self, och, true);
uint32_t lch = self->last_graphic_char;
draw_text(self, &ch, 1);
if (!from_input_stream) self->last_graphic_char = lch;
}
void
@ -1908,7 +1911,9 @@ screen_repeat_character(Screen *self, unsigned int count) {
if (self->last_graphic_char) {
if (count == 0) count = 1;
unsigned int num = MIN(count, CSI_REP_MAX_REPETITIONS);
while (num-- > 0) draw_codepoint(self, self->last_graphic_char, false);
alignas(64) uint32_t buf[64];
for (unsigned i = 0; i < arraysz(buf); i++) buf[i] = self->last_graphic_char;
for (unsigned i = 0; i < num; i += arraysz(buf)) screen_draw_text(self, buf, MIN(num - i, arraysz(buf)));
}
}

View file

@ -9,7 +9,6 @@
#include "vt-parser.h"
#include "graphics.h"
#include "monotonic.h"
#include "simd-string.h"
typedef enum ScrollTypes { SCROLL_LINE = -999999, SCROLL_PAGE, SCROLL_FULL } ScrollType;
@ -167,8 +166,7 @@ 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, uint32_t codepoint);
void screen_draw_printable_ascii(Screen *self, ByteLoader* );
void screen_draw_text(Screen *self, const uint32_t *chars, size_t num_chars);
void screen_ensure_bounds(Screen *self, bool use_margins, bool cursor_was_within_margins);
void screen_toggle_screen_buffer(Screen *self, bool, bool);
void screen_normal_keypad_mode(Screen *self);

View file

@ -7,6 +7,7 @@
#define SIMDE_ENABLE_NATIVE_ALIASES
#include "data-types.h"
#include "charsets.h"
#include "simd-string.h"
#ifdef __clang__
_Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wbitwise-instead-of-logical\"")
@ -197,6 +198,46 @@ find_byte_not_in_range(const uint8_t *haystack, const size_t sz, const uint8_t a
}
// }}}
// UTF-8 {{{
static unsigned
utf8_decode_to_sentinel_scalar(UTF8Decoder *d, const uint8_t *src, const size_t src_sz, const uint8_t sentinel) {
unsigned num_consumed = 0, num_output = 0;
while (num_consumed < src_sz && num_output < arraysz(d->output)) {
const uint8_t ch = src[num_consumed++];
if (ch < ' ') {
zero_at_ptr(&d->state);
if (num_output) { d->output_chars_callback(d->callback_data, d->output, num_output); num_output = 0; }
d->control_byte_callback(d->callback_data, ch);
if (ch == sentinel) break;
} else {
switch(decode_utf8(&d->state.cur, &d->state.codep, ch)) {
case UTF8_ACCEPT:
d->output[num_output++] = d->state.codep;
break;
case UTF8_REJECT: {
const bool prev_was_accept = d->state.prev == UTF8_ACCEPT;
zero_at_ptr(&d->state);
d->output[num_output++] = 0xfffd;
if (!prev_was_accept) {
num_consumed--;
continue; // so that prev is correct
}
} break;
}
}
d->state.prev = d->state.cur;
}
if (num_output) d->output_chars_callback(d->callback_data, d->output, num_output);
return num_consumed;
}
unsigned
utf8_decode_to_sentinel(UTF8Decoder *d, const uint8_t *src, const size_t src_sz, const uint8_t sentinel) {
return utf8_decode_to_sentinel_scalar(d, src, src_sz, sentinel);
}
// }}}
bool
init_simd(void *x) {
PyObject *module = (PyObject*)x;

View file

@ -9,6 +9,8 @@
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdalign.h>
#include "data-types.h"
#define BYTE_LOADER_T unsigned long long
typedef struct ByteLoader {
@ -16,12 +18,24 @@ typedef struct ByteLoader {
unsigned sz_of_next_load, digits_left, num_left;
const uint8_t *next_load_at;
} ByteLoader;
uint8_t byte_loader_peek(const ByteLoader *self);
void byte_loader_init(ByteLoader *self, const uint8_t *buf, unsigned int sz);
uint8_t byte_loader_next(ByteLoader *self);
typedef void (*control_byte_callback)(void *data, uint8_t ch);
typedef void (*output_chars_callback)(void *data, const uint32_t *chars, unsigned count);
typedef struct UTF8Decoder {
struct { uint32_t cur, prev, codep; } state;
alignas(64) uint32_t output[64];
void *callback_data;
control_byte_callback control_byte_callback;
output_chars_callback output_chars_callback;
} UTF8Decoder;
static inline void utf8_decoder_reset(UTF8Decoder *self) { zero_at_ptr(&self->state); }
unsigned utf8_decode_to_sentinel(UTF8Decoder *d, const uint8_t *src, const size_t src_sz, const uint8_t sentinel);
// Pass a PyModule PyObject* as the argument. Must be called once at application startup
bool init_simd(void* module);

View file

@ -9,7 +9,6 @@
// TODO: Test screen_request_capabilities
#include "vt-parser.h"
#include "charsets.h"
#include "screen.h"
#include "control-codes.h"
#include "state.h"
@ -28,7 +27,7 @@
// Macros {{{
#define SET_STATE(x) \
self->vte_state = VTE_##x; if (VTE_##x == VTE_NORMAL) { zero_at_ptr(&self->utf8); }
self->vte_state = VTE_##x;
#define DIGIT '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9'
@ -66,8 +65,8 @@ _report_params(PyObject *dump_callback, id_type window_id, const char *name, int
unsigned int i, p=0;
if (r) p += snprintf(buf + p, sizeof(buf) - 2, "%u %u %u %u ", r->top, r->left, r->bottom, r->right);
const char *fmt = is_group ? "%i:" : "%i ";
for(i = 0; i < count && p < MAX_CSI_PARAMS*3-20; i++) {
int n = snprintf(buf + p, MAX_CSI_PARAMS*3 - p, fmt, params[i]);
for(i = 0; i < count && p < arraysz(buf)-20; i++) {
int n = snprintf(buf + p, arraysz(buf) - p, fmt, params[i]);
if (n < 0) break;
p += n;
}
@ -75,6 +74,15 @@ _report_params(PyObject *dump_callback, id_type window_id, const char *name, int
Py_XDECREF(PyObject_CallFunction(dump_callback, "Kss", window_id, name, buf)); PyErr_Clear();
}
static void
_report_draw(PyObject *dump_callback, id_type window_id, const uint32_t *chars, unsigned num) {
RAII_PyObject(s, PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, chars, num));
if (s) {
RAII_PyObject(t, PyObject_CallFunction(dump_callback, "KsO", window_id, "draw", s));
if (t == NULL) PyErr_Clear();
}
}
#define DUMP_UNUSED
#define REPORT_ERROR(...) _report_error(self->dump_callback, self->window_id, __VA_ARGS__);
@ -92,11 +100,7 @@ _report_params(PyObject *dump_callback, id_type window_id, const char *name, int
#define REPORT_COMMAND(...) GET_MACRO(__VA_ARGS__, REPORT_COMMAND3, REPORT_COMMAND2, REPORT_COMMAND1, SENTINEL)(__VA_ARGS__)
#define REPORT_VA_COMMAND(...) Py_XDECREF(PyObject_CallFunction(self->dump_callback, __VA_ARGS__)); PyErr_Clear();
#define REPORT_DRAW(ch) \
Py_XDECREF(PyObject_CallFunction(self->dump_callback, "KsC", self->window_id, "draw", ch)); PyErr_Clear();
#define REPORT_DRAW_ASCII(ch, sz) \
Py_XDECREF(PyObject_CallFunction(self->dump_callback, "Kss#", self->window_id, "draw", ch, (Py_ssize_t)sz)); PyErr_Clear();
#define REPORT_DRAW(chars, num) _report_draw(self->dump_callback, self->window_id, chars, num);
#define REPORT_PARAMS(name, params, num, is_group, region) _report_params(self->dump_callback, self->window_id, name, params, num_params, is_group, region)
@ -113,8 +117,7 @@ _report_params(PyObject *dump_callback, id_type window_id, const char *name, int
#define REPORT_ERROR(...) log_error(ERROR_PREFIX " " __VA_ARGS__);
#define REPORT_COMMAND(...)
#define REPORT_VA_COMMAND(...)
#define REPORT_DRAW(ch)
#define REPORT_DRAW_ASCII(ch, sz)
#define REPORT_DRAW(chars, num)
#define REPORT_PARAMS(...)
#define REPORT_OSC(name, string)
#define REPORT_OSC2(name, code, string)
@ -188,7 +191,7 @@ typedef struct ParsedCSI {
typedef struct PS {
id_type window_id;
struct { UTF8State prev, state; uint32_t codep; } utf8;
UTF8Decoder utf8_decoder;
VTEState vte_state;
ParsedCSI csi;
@ -223,79 +226,45 @@ reset_csi(ParsedCSI *csi) {
// Normal mode {{{
static void
draw_byte(PS *self, const uint8_t b) {
switch (decode_utf8(&self->utf8.state, &self->utf8.codep, b)) {
case UTF8_ACCEPT:
REPORT_DRAW(self->utf8.codep);
screen_draw(self->screen, self->utf8.codep);
break;
case UTF8_REJECT: {
bool prev_was_accept = self->utf8.prev == UTF8_ACCEPT;
zero_at_ptr(&self->utf8);
REPORT_DRAW(0xfffd);
screen_draw(self->screen, 0xfffd);
if (!prev_was_accept) {
draw_byte(self, b);
return; // so that prev is correct
}
} break;
}
self->utf8.prev = self->utf8.state;
}
static void
dispatch_normal_mode_byte(PS *self, uint8_t ch) {
dispatch_single_byte_control(void *s, uint8_t ch) {
#define CALL_SCREEN_HANDLER(name) REPORT_COMMAND(name); name(self->screen); break;
if (ch < ' ') {
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 SI:
REPORT_ERROR("Ignoring request to change charset as we only support UTF-8"); break;
case SO:
REPORT_ERROR("Ignoring request to change charset as we only support UTF-8"); break;
case ESC:
SET_STATE(ESC); break;
break;
default:
break;
}
} else {
draw_byte(self, ch);
PS *self = s;
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 SI:
REPORT_ERROR("Ignoring request to change charset as we only support UTF-8"); break;
case SO:
REPORT_ERROR("Ignoring request to change charset as we only support UTF-8"); break;
case ESC:
SET_STATE(ESC); break;
default:
break;
}
#undef CALL_SCREEN_HANDLER
}
static void
dispatch_printable_ascii(PS *self, const size_t sz) {
REPORT_DRAW_ASCII(self->buf + self->read.pos, sz);
ByteLoader b; byte_loader_init(&b, self->buf + self->read.pos, sz);
screen_draw_printable_ascii(self->screen, &b);
self->read.pos += sz;
dispatch_output_chars(void *s, const uint32_t *chars, unsigned sz) {
PS *self = s;
REPORT_DRAW(chars, sz);
screen_draw_text(self->screen, chars, sz);
}
static void
consume_normal(PS *self) {
do {
if (self->utf8.state == UTF8_ACCEPT) {
size_t sz = self->read.sz - self->read.pos;
const uint8_t *p = find_byte_not_in_range(self->buf + self->read.pos, sz, 32, 126);
if (p != NULL) sz = p - (self->buf + self->read.pos);
if (sz) dispatch_printable_ascii(self, sz);
else dispatch_normal_mode_byte(self, self->buf[self->read.pos++]);
} else {
dispatch_normal_mode_byte(self, self->buf[self->read.pos++]);
}
self->read.pos += utf8_decode_to_sentinel(&self->utf8_decoder, self->buf + self->read.pos, self->read.sz - self->read.pos, ESC);
} while (self->read.pos < self->read.sz && self->vte_state == VTE_NORMAL);
}
// }}}
@ -829,7 +798,7 @@ csi_parse_loop(PS *self, ParsedCSI *csi, const uint8_t *buf, size_t *pos, const
case CSI_START:
switch (ch) {
case CSI_NORMAL_MODE_EMBEDDINGS:
dispatch_normal_mode_byte(self, ch); break;
dispatch_single_byte_control(self, ch); break;
case ';':
csi->params[csi->num_params++] = 0;
csi->state = CSI_BODY;
@ -867,7 +836,7 @@ csi_parse_loop(PS *self, ParsedCSI *csi, const uint8_t *buf, size_t *pos, const
case CSI_POST_SECONDARY:
switch (ch) {
case CSI_NORMAL_MODE_EMBEDDINGS:
dispatch_normal_mode_byte(self, ch); break;
dispatch_single_byte_control(self, ch); break;
case CSI_TRAILER:
csi->is_valid = true;
csi->trailer = ch;
@ -880,7 +849,7 @@ csi_parse_loop(PS *self, ParsedCSI *csi, const uint8_t *buf, size_t *pos, const
case CSI_BODY:
switch(ch) {
case CSI_NORMAL_MODE_EMBEDDINGS:
dispatch_normal_mode_byte(self, ch); break;
dispatch_single_byte_control(self, ch); break;
case CSI_SECONDARY:
if (ch == '-' && csi->num_digits == 0) {
csi->mult = -1; csi->num_digits = 1;
@ -1605,12 +1574,16 @@ run_worker(void *p, ParseData *pd, bool flush) {
if (ret) { Py_DECREF(ret); } else { PyErr_Clear(); }
}
#endif
self->screen = p;
if (self->read.pos < self->read.sz) {
self->dump_callback = pd->dump_callback; self->now = pd->now;
pd->time_since_new_input = pd->now - self->new_input_at;
if (flush || pd->time_since_new_input >= OPT(input_delay) || (BUF_SZ - self->read.sz) <= 16 * 1024) {
pd->input_read = true;
self->dump_callback = pd->dump_callback; self->now = pd->now;
self->screen = p;
// these are here as they need to be specialized to dump/non dump versions
self->utf8_decoder.control_byte_callback = dispatch_single_byte_control;
self->utf8_decoder.output_chars_callback = dispatch_output_chars;
self->utf8_decoder.callback_data = self;
do {
end_with_lock; {
do_parse_vt(self);
@ -1704,7 +1677,7 @@ static void
reset(PS *self) {
SET_STATE(NORMAL);
reset_csi(&self->csi);
utf8_decoder_reset(&self->utf8_decoder);
zero_at_ptr(&self->pending_mode);
self->pending_mode.wait_time = DEFAULT_PENDING_WAIT_TIME;
}
@ -1756,9 +1729,10 @@ alloc_vt_parser(id_type window_id) {
Py_CLEAR(self); PyErr_Format(PyExc_RuntimeError, "Failed to create Parser lock mutex: %s", strerror(ret));
return NULL;
}
state->csi.mult = 1;
state->window_id = window_id;
state->pending_mode.wait_time = DEFAULT_PENDING_WAIT_TIME;
utf8_decoder_reset(&state->utf8_decoder);
reset_csi(&state->csi);
}
return self;
}