diff --git a/kitty/line-buf.c b/kitty/line-buf.c index 7170b4fce..de0a8fe1d 100644 --- a/kitty/line-buf.c +++ b/kitty/line-buf.c @@ -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); diff --git a/kitty/line.c b/kitty/line.c index 8463096d5..8b2910ada 100644 --- a/kitty/line.c +++ b/kitty/line.c @@ -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; } diff --git a/kitty/lineops.h b/kitty/lineops.h index 22dc9e8b5..241e34ac1 100644 --- a/kitty/lineops.h +++ b/kitty/lineops.h @@ -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); diff --git a/kitty/screen.c b/kitty/screen.c index d436298b5..5b872b8c3 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -25,6 +25,7 @@ #include "modes.h" #include "wcwidth-std.h" #include "wcswidth.h" +#include #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))); } } diff --git a/kitty/screen.h b/kitty/screen.h index d29c3511f..177731d41 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -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); diff --git a/kitty/simd-string.c b/kitty/simd-string.c index 7a04aebc2..7c57b6fd9 100644 --- a/kitty/simd-string.c +++ b/kitty/simd-string.c @@ -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; diff --git a/kitty/simd-string.h b/kitty/simd-string.h index a8450d8ba..732e9d01b 100644 --- a/kitty/simd-string.h +++ b/kitty/simd-string.h @@ -9,6 +9,8 @@ #include #include #include +#include +#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); diff --git a/kitty/vt-parser.c b/kitty/vt-parser.c index 56eb7dd1c..019d16b8a 100644 --- a/kitty/vt-parser.c +++ b/kitty/vt-parser.c @@ -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; }