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:
parent
b41cf52ce4
commit
718f4b328f
8 changed files with 270 additions and 230 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
283
kitty/screen.c
283
kitty/screen.c
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue