diff --git a/kitty/data-types.h b/kitty/data-types.h index 65d158ef2..758ffcab7 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -59,6 +59,7 @@ static inline PyObject* Py_XNewRef(PyObject *o) { Py_XINCREF(o); return o; } typedef unsigned long long id_type; typedef uint32_t char_type; +#define MAX_CHAR_TYPE_VALUE UINT32_MAX typedef uint32_t color_type; typedef uint16_t hyperlink_id_type; typedef int key_type; @@ -119,7 +120,6 @@ typedef struct ImageAnchorPosition { #define BLANK_CHAR 0 #define COL_MASK 0xFFFFFFFF #define DECORATION_FG_CODE 58 -#define CHAR_IS_BLANK(ch) ((ch) == 32 || (ch) == 0) // PUA character used as an image placeholder. #define IMAGE_PLACEHOLDER_CHAR 0x10EEEE @@ -197,14 +197,6 @@ typedef struct { uint32_t left, top, right, bottom; } Region; -typedef union CharOrIndex { - struct { - char_type ch_is_index: 1; - char_type ch: sizeof(char_type) - 1; - }; - char_type val; -} CharOrIndex; - typedef enum { UNKNOWN_PROMPT_KIND = 0, PROMPT_START = 1, SECONDARY_PROMPT = 2, OUTPUT_START = 3 } PromptKind; typedef struct {int x;} *HYPERLINK_POOL_HANDLE; typedef struct { diff --git a/kitty/fontconfig.c b/kitty/fontconfig.c index bfc081ca7..a9a3008dd 100644 --- a/kitty/fontconfig.c +++ b/kitty/fontconfig.c @@ -494,7 +494,7 @@ end: static bool face_has_codepoint(const void *face, char_type cp) { return glyph_id_for_codepoint(face, cp) > 0; } PyObject* -create_fallback_face(PyObject UNUSED *base_face, CPUCell* cell, bool bold, bool italic, bool emoji_presentation, FONTS_DATA_HANDLE fg) { +create_fallback_face(PyObject UNUSED *base_face, const ListOfChars *lc, bool bold, bool italic, bool emoji_presentation, FONTS_DATA_HANDLE fg) { ensure_initialized(); PyObject *ans = NULL; RAII_PyObject(d, NULL); @@ -505,7 +505,7 @@ create_fallback_face(PyObject UNUSED *base_face, CPUCell* cell, bool bold, bool if (!emoji_presentation && bold) { AP(FcPatternAddInteger, FC_WEIGHT, FC_WEIGHT_BOLD, "weight"); } if (!emoji_presentation && italic) { AP(FcPatternAddInteger, FC_SLANT, FC_SLANT_ITALIC, "slant"); } if (emoji_presentation) { AP(FcPatternAddBool, FC_COLOR, true, "color"); } - size_t num = cell_as_unicode_for_fallback(cell, char_buf); + size_t num = cell_as_unicode_for_fallback(lc, char_buf); add_charset(pat, num); d = _fc_match(pat); face_from_descriptor: @@ -515,22 +515,22 @@ face_from_descriptor: while ((q = iter_fallback_faces(fg, &idx))) { if (face_equals_descriptor(q, d)) { ans = PyLong_FromSsize_t(idx); - if (!glyph_found) glyph_found = has_cell_text(face_has_codepoint, q, cell, false); + if (!glyph_found) glyph_found = has_cell_text(face_has_codepoint, q, false, lc); goto end; } } ans = face_from_descriptor(d, fg); - if (!glyph_found && ans) glyph_found = has_cell_text(face_has_codepoint, ans, cell, false); + if (!glyph_found && ans) glyph_found = has_cell_text(face_has_codepoint, ans, false, lc); } end: Py_CLEAR(d); if (pat != NULL) { FcPatternDestroy(pat); pat = NULL; } if (!glyph_found && !PyErr_Occurred()) { - if (builtin_nerd_font.face && has_cell_text(face_has_codepoint, builtin_nerd_font.face, cell, false)) { + if (builtin_nerd_font.face && has_cell_text(face_has_codepoint, builtin_nerd_font.face, false, lc)) { Py_CLEAR(ans); d = builtin_nerd_font.descriptor; Py_INCREF(d); glyph_found = true; goto face_from_descriptor; } else { - if (global_state.debug_font_fallback && ans) has_cell_text(face_has_codepoint, ans, cell, true); + if (global_state.debug_font_fallback && ans) has_cell_text(face_has_codepoint, ans, true, lc); Py_CLEAR(ans); ans = Py_None; Py_INCREF(ans); } } diff --git a/kitty/fonts.c b/kitty/fonts.c index f9a99cbbd..201548a0f 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -8,6 +8,7 @@ #include "fonts.h" #include "pyport.h" +#include "charsets.h" #include "state.h" #include "emoji.h" #include "unicode-data.h" @@ -465,36 +466,38 @@ face_has_codepoint(const void* face, char_type cp) { } static bool -has_emoji_presentation(CPUCell *cpu_cell, GPUCell *gpu_cell) { - return gpu_cell->attrs.width == 2 && is_emoji(cpu_cell->ch) && cpu_cell->cc_idx[0] != VS15; +has_emoji_presentation(const GPUCell *gpu_cell, const ListOfChars *lc) { + if (gpu_cell->attrs.width != 2) return false; + return lc->count > 1 && is_emoji(lc->chars[0]) && lc->chars[1] != VS15; } bool -has_cell_text(bool(*has_codepoint)(const void*, char_type ch), const void* face, const CPUCell *cell, bool do_debug) { - if (!has_codepoint(face, cell->ch)) goto not_found; - char_type combining_chars[arraysz(cell->cc_idx)]; +has_cell_text(bool(*has_codepoint)(const void*, char_type ch), const void* face, bool do_debug, const ListOfChars *lc) { + if (!has_codepoint(face, lc->chars[0])) goto not_found; unsigned num_cc = 0; - for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) { - const char_type ccp = codepoint_for_mark(cell->cc_idx[i]); - if (!is_non_rendered_char(ccp)) combining_chars[num_cc++] = ccp; + for (unsigned i = 1; i < lc->count; i++) { + if (!is_non_rendered_char(lc->chars[i])) lc->chars[i] = 0; + else num_cc++; } if (num_cc == 0) return true; if (num_cc == 1) { - if (has_codepoint(face, combining_chars[0])) return true; + char_type cc = 0; + for (unsigned i = 1; i < lc->count && cc; i++) cc = lc->chars[i]; + if (has_codepoint(face, cc)) return true; char_type ch = 0; - if (hb_unicode_compose(hb_unicode_funcs_get_default(), cell->ch, combining_chars[0], &ch) && face_has_codepoint(face, ch)) return true; + if (hb_unicode_compose(hb_unicode_funcs_get_default(), lc->chars[0], cc, &ch) && face_has_codepoint(face, ch)) return true; goto not_found; } - for (unsigned i = 0; i < num_cc; i++) { - if (!has_codepoint(face, combining_chars[i])) goto not_found; + for (unsigned i = 1; i < lc->count; i++) { + if (lc->chars[i] && !has_codepoint(face, lc->chars[i])) goto not_found; } return true; not_found: if (do_debug) { debug("The font chosen by the OS for the text: "); - debug("U+%x ", cell->ch); - for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) { - debug("U+%x ", codepoint_for_mark(cell->cc_idx[i])); + debug("U+%x ", lc->chars[0]); + for (unsigned i = 1; i < lc->count; i++) { + if (lc->chars[i]) debug("U+%x ", lc->chars[i]); } debug("is "); PyObject_Print((PyObject*)face, stderr, 0); debug(" but it does not actually contain glyphs for that text\n"); @@ -503,11 +506,9 @@ not_found: } static void -output_cell_fallback_data(CPUCell *cell, bool bold, bool italic, bool emoji_presentation, PyObject *face) { - debug("U+%x ", cell->ch); - for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) { - debug("U+%x ", codepoint_for_mark(cell->cc_idx[i])); - } +output_cell_fallback_data(const ListOfChars *lc, bool bold, bool italic, bool emoji_presentation, PyObject *face) { + debug("U+%x ", lc->chars[0]); + for (unsigned i = 1; i < lc->count; i++) debug("U+%x ", lc->chars[i]); if (bold) debug("bold "); if (italic) debug("italic "); if (emoji_presentation) debug("emoji_presentation "); @@ -527,7 +528,7 @@ iter_fallback_faces(FONTS_DATA_HANDLE fgh, ssize_t *idx) { } static ssize_t -load_fallback_font(FontGroup *fg, CPUCell *cell, bool bold, bool italic, bool emoji_presentation) { +load_fallback_font(FontGroup *fg, const ListOfChars *lc, bool bold, bool italic, bool emoji_presentation) { if (fg->fallback_fonts_count > 100) { log_error("Too many fallback fonts"); return MISSING_FONT; } ssize_t f; @@ -535,10 +536,10 @@ load_fallback_font(FontGroup *fg, CPUCell *cell, bool bold, bool italic, bool em else f = italic ? fg->italic_font_idx : fg->medium_font_idx; if (f < 0) f = fg->medium_font_idx; - PyObject *face = create_fallback_face(fg->fonts[f].face, cell, bold, italic, emoji_presentation, (FONTS_DATA_HANDLE)fg); + PyObject *face = create_fallback_face(fg->fonts[f].face, lc, bold, italic, emoji_presentation, (FONTS_DATA_HANDLE)fg); if (face == NULL) { PyErr_Print(); return MISSING_FONT; } if (face == Py_None) { Py_DECREF(face); return MISSING_FONT; } - if (global_state.debug_font_fallback) output_cell_fallback_data(cell, bold, italic, emoji_presentation, face); + if (global_state.debug_font_fallback) output_cell_fallback_data(lc, bold, italic, emoji_presentation, face); if (PyLong_Check(face)) { ssize_t ans = fg->first_fallback_font_idx + PyLong_AsSsize_t(face); Py_DECREF(face); return ans; } set_size_for_face(face, fg->cell_height, true, (FONTS_DATA_HANDLE)fg); @@ -552,18 +553,30 @@ load_fallback_font(FontGroup *fg, CPUCell *cell, bool bold, bool italic, bool em return ans; } +size_t +chars_as_utf8(const ListOfChars *lc, char *buf, char_type zero_char) { + size_t n; + if (lc->count == 1) n = encode_utf8(lc->chars[0] ? lc->chars[0] : zero_char, buf); + else { + n = encode_utf8(lc->chars[0], buf); + if (lc->chars[0] != '\t') for (unsigned i = 1; i < lc->count; i++) n += encode_utf8(lc->chars[i], buf + n); + } + buf[n] = 0; + return n; +} + static ssize_t -fallback_font(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell) { +fallback_font(FontGroup *fg, const GPUCell *gpu_cell, const ListOfChars *lc) { bool bold = gpu_cell->attrs.bold; bool italic = gpu_cell->attrs.italic; - bool emoji_presentation = has_emoji_presentation(cpu_cell, gpu_cell); + bool emoji_presentation = has_emoji_presentation(gpu_cell, lc); char style = emoji_presentation ? 'a' : 'A'; if (bold) style += italic ? 3 : 2; else style += italic ? 1 : 0; - char cell_text[8 + arraysz(cpu_cell->cc_idx) * 4] = {style}; - const size_t cell_text_len = 1 + cell_as_utf8(cpu_cell, true, cell_text + 1, ' '); + char cell_text[4 * 32] = {style}; + const size_t cell_text_len = 1 + chars_as_utf8(lc, cell_text + 1, ' '); fallback_font_map_t_itr fi = vt_get(&fg->fallback_font_map, cell_text); if (!vt_is_end(fi)) return fi.data->val; - ssize_t idx = load_fallback_font(fg, cpu_cell, bold, italic, emoji_presentation); + ssize_t idx = load_fallback_font(fg, lc, bold, italic, emoji_presentation); const char *alloced_key = strndup(cell_text, cell_text_len); if (alloced_key) vt_insert(&fg->fallback_font_map, alloced_key, idx); return idx; @@ -587,12 +600,13 @@ in_symbol_maps(FontGroup *fg, char_type ch) { // - BOX_FONT // - an index in the fonts list static ssize_t -font_for_cell(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell, bool *is_main_font, bool *is_emoji_presentation) { +font_for_cell(FontGroup *fg, const CPUCell *cpu_cell, const GPUCell *gpu_cell, bool *is_main_font, bool *is_emoji_presentation, TextCache *tc, ListOfChars *lc) { *is_main_font = false; *is_emoji_presentation = false; + text_in_cell(cpu_cell, tc, lc); START_ALLOW_CASE_RANGE ssize_t ans; - switch(cpu_cell->ch) { + switch(lc->chars[0]) { case 0: case ' ': case 0x2002: // en-space @@ -611,8 +625,8 @@ START_ALLOW_CASE_RANGE case 0xf5d0 ... 0xf60d: // branch drawing characters return BOX_FONT; default: - *is_emoji_presentation = has_emoji_presentation(cpu_cell, gpu_cell); - ans = in_symbol_maps(fg, cpu_cell->ch); + *is_emoji_presentation = has_emoji_presentation(gpu_cell, lc); + ans = in_symbol_maps(fg, lc->chars[0]); if (ans > -1) return ans; switch(gpu_cell->attrs.bold | (gpu_cell->attrs.italic << 1)) { case 0: @@ -625,8 +639,8 @@ START_ALLOW_CASE_RANGE ans = fg->bi_font_idx; break; } if (ans < 0) ans = fg->medium_font_idx; - if (!*is_emoji_presentation && has_cell_text((bool(*)(const void*, char_type))face_has_codepoint, (fg->fonts + ans)->face, cpu_cell, false)) { *is_main_font = true; return ans; } - return fallback_font(fg, cpu_cell, gpu_cell); + if (!*is_emoji_presentation && has_cell_text((bool(*)(const void*, char_type))face_has_codepoint, (fg->fonts + ans)->face, false, lc)) { *is_main_font = true; return ans; } + return fallback_font(fg, gpu_cell, lc); } END_ALLOW_CASE_RANGE } @@ -674,9 +688,10 @@ render_alpha_mask(const uint8_t *alpha_mask, pixel* dest, Region *src_rect, Regi } static void -render_box_cell(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell) { +render_box_cell(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell, const TextCache *tc) { int error = 0; - glyph_index glyph = box_glyph_id(cpu_cell->ch); + char_type ch = cell_first_char(cpu_cell, tc); + glyph_index glyph = box_glyph_id(ch); SpritePosition *sp = sprite_position_for(fg, &fg->fonts[BOX_FONT], &glyph, 1, 0, 1, &error); if (sp == NULL) { sprite_map_set_error(error); PyErr_Print(); @@ -687,7 +702,7 @@ render_box_cell(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell) { if (sp->rendered) return; sp->rendered = true; sp->colored = false; - PyObject *ret = PyObject_CallFunction(box_drawing_function, "IIId", cpu_cell->ch, fg->cell_width, fg->cell_height, (fg->logical_dpi_x + fg->logical_dpi_y) / 2.0); + PyObject *ret = PyObject_CallFunction(box_drawing_function, "IIId", (unsigned int)ch, fg->cell_width, fg->cell_height, (fg->logical_dpi_x + fg->logical_dpi_y) / 2.0); if (ret == NULL) { PyErr_Print(); return; } uint8_t *alpha_mask = PyLong_AsVoidPtr(PyTuple_GET_ITEM(ret, 0)); ensure_canvas_can_fit(fg, 1); @@ -698,18 +713,19 @@ render_box_cell(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell) { } static void -load_hb_buffer(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells) { +load_hb_buffer(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, const TextCache *tc) { index_type num; hb_buffer_clear_contents(harfbuzz_buffer); + RAII_ListOfChars(lc); while (num_cells) { uint16_t prev_width = 0; - for (num = 0; num_cells && num < arraysz(shape_buffer) - 20 - arraysz(first_cpu_cell->cc_idx); first_cpu_cell++, first_gpu_cell++, num_cells--) { + for (num = 0; num_cells; first_cpu_cell++, first_gpu_cell++, num_cells--) { if (prev_width == 2) { prev_width = 0; continue; } - shape_buffer[num++] = first_cpu_cell->ch; + text_in_cell(first_cpu_cell, tc, &lc); + if (lc.count + num > arraysz(shape_buffer)) break; + memcpy(shape_buffer + num, lc.chars, lc.count * sizeof(shape_buffer[0])); + num += lc.count; prev_width = first_gpu_cell->attrs.width; - for (unsigned i = 0; i < arraysz(first_cpu_cell->cc_idx) && first_cpu_cell->cc_idx[i]; i++) { - shape_buffer[num++] = codepoint_for_mark(first_cpu_cell->cc_idx[i]); - } } hb_buffer_add_utf32(harfbuzz_buffer, shape_buffer, num, 0, num); } @@ -740,7 +756,7 @@ typedef struct GlyphRenderScratch { static GlyphRenderScratch global_glyph_render_scratch = {0}; static void -render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPUCell *cpu_cells, GPUCell *gpu_cells, hb_glyph_info_t *info, hb_glyph_position_t *positions, Font *font, glyph_index *glyphs, unsigned glyph_count, bool center_glyph) { +render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPUCell *cpu_cells, GPUCell *gpu_cells, hb_glyph_info_t *info, hb_glyph_position_t *positions, Font *font, glyph_index *glyphs, unsigned glyph_count, bool center_glyph, const TextCache *tc) { #define sp global_glyph_render_scratch.sprite_positions int error = 0; bool all_rendered = true; @@ -761,7 +777,7 @@ render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPU } ensure_canvas_can_fit(fg, num_cells + 1); - bool was_colored = gpu_cells->attrs.width == 2 && is_emoji(cpu_cells->ch); + bool was_colored = gpu_cells->attrs.width == 2 && is_emoji(cell_first_char(cpu_cells, tc)); render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, fg->canvas.buf, fg->cell_width, fg->cell_height, num_cells, fg->baseline, &was_colored, (FONTS_DATA_HANDLE)fg, center_glyph); if (PyErr_Occurred()) PyErr_Print(); @@ -804,28 +820,23 @@ typedef struct { static GroupState group_state = {0}; -static unsigned int -num_codepoints_in_cell(CPUCell *cell) { - unsigned int ans = 1; - for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) ans++; - return ans; -} - static void -shape(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, hb_font_t *font, Font *fobj, bool disable_ligature) { +shape(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, hb_font_t *font, Font *fobj, bool disable_ligature, const TextCache *tc) { if (group_state.groups_capacity <= 2 * num_cells) { group_state.groups_capacity = MAX(128u, 2 * num_cells); // avoid unnecessary reallocs group_state.groups = realloc(group_state.groups, sizeof(Group) * group_state.groups_capacity); if (!group_state.groups) fatal("Out of memory"); } + RAII_ListOfChars(lc); + text_in_cell(first_cpu_cell, tc, &lc); group_state.previous_cluster = UINT32_MAX; group_state.prev_was_special = false; group_state.prev_was_empty = false; group_state.current_cell_data.cpu_cell = first_cpu_cell; group_state.current_cell_data.gpu_cell = first_gpu_cell; - group_state.current_cell_data.num_codepoints = num_codepoints_in_cell(first_cpu_cell); + group_state.current_cell_data.num_codepoints = MAX(1, lc.count); group_state.current_cell_data.codepoints_consumed = 0; - group_state.current_cell_data.current_codepoint = first_cpu_cell->ch; + group_state.current_cell_data.current_codepoint = lc.chars[0]; zero_at_ptr_count(group_state.groups, group_state.groups_capacity); group_state.group_idx = 0; group_state.glyph_idx = 0; @@ -835,7 +846,7 @@ shape(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, hb group_state.first_gpu_cell = first_gpu_cell; group_state.last_cpu_cell = first_cpu_cell + (num_cells ? num_cells - 1 : 0); group_state.last_gpu_cell = first_gpu_cell + (num_cells ? num_cells - 1 : 0); - load_hb_buffer(first_cpu_cell, first_gpu_cell, num_cells); + load_hb_buffer(first_cpu_cell, first_gpu_cell, num_cells, tc); size_t num_features = fobj->num_ffs_hb_features; if (num_features && !disable_ligature) num_features--; // the last feature is always -calt @@ -877,7 +888,7 @@ is_empty_glyph(glyph_index glyph_id, Font *font) { } static unsigned int -check_cell_consumed(CellData *cell_data, CPUCell *last_cpu_cell) { +check_cell_consumed(CellData *cell_data, CPUCell *last_cpu_cell, const TextCache *tc) { cell_data->codepoints_consumed++; if (cell_data->codepoints_consumed >= cell_data->num_codepoints) { uint16_t width = cell_data->gpu_cell->attrs.width; @@ -885,20 +896,22 @@ check_cell_consumed(CellData *cell_data, CPUCell *last_cpu_cell) { cell_data->gpu_cell += MAX(1, width); cell_data->codepoints_consumed = 0; if (cell_data->cpu_cell <= last_cpu_cell) { - cell_data->num_codepoints = num_codepoints_in_cell(cell_data->cpu_cell); - cell_data->current_codepoint = cell_data->cpu_cell->ch; + cell_data->num_codepoints = num_codepoints_in_cell(cell_data->cpu_cell, tc); + cell_data->current_codepoint = cell_first_char(cell_data->cpu_cell, tc); } else cell_data->current_codepoint = 0; return width; } else { switch(cell_data->codepoints_consumed) { case 0: - cell_data->current_codepoint = cell_data->cpu_cell->ch; + cell_data->current_codepoint = cell_first_char(cell_data->cpu_cell, tc); break; default: { - index_type mark = cell_data->cpu_cell->cc_idx[cell_data->codepoints_consumed - 1]; + RAII_ListOfChars(lc); + text_in_cell(cell_data->cpu_cell, tc, &lc); + char_type cc = lc.chars[cell_data->codepoints_consumed]; // VS15/16 cause rendering to break, as they get marked as // special glyphs, so map to 0, to avoid that - cell_data->current_codepoint = (mark == VS15 || mark == VS16) ? 0 : codepoint_for_mark(mark); + cell_data->current_codepoint = (cc == VS15 || cc == VS16) ? 0 : cc; break; } } @@ -927,11 +940,12 @@ ligature_type_from_glyph_name(const char *glyph_name, SpacerStrategy strategy) { #define G(x) (group_state.x) static void -detect_spacer_strategy(hb_font_t *hbf, Font *font) { - CPUCell cpu_cells[3] = {{.ch = '='}, {.ch = '='}, {.ch = '='}}; +detect_spacer_strategy(hb_font_t *hbf, Font *font, const TextCache *tc) { + CPUCell cpu_cells[3] = {0}; + for (unsigned i = 0; i < arraysz(cpu_cells); i++) cell_set_char(&cpu_cells[i], '='); const CellAttrs w1 = {.width=1}; GPUCell gpu_cells[3] = {{.attrs = w1}, {.attrs = w1}, {.attrs = w1}}; - shape(cpu_cells, gpu_cells, arraysz(cpu_cells), hbf, font, false); + shape(cpu_cells, gpu_cells, arraysz(cpu_cells), hbf, font, false, tc); font->spacer_strategy = SPACERS_BEFORE; if (G(num_glyphs) > 1) { glyph_index glyph_id = G(info)[G(num_glyphs) - 1].codepoint; @@ -939,7 +953,7 @@ detect_spacer_strategy(hb_font_t *hbf, Font *font) { bool is_empty = is_special && is_empty_glyph(glyph_id, font); if (is_empty) font->spacer_strategy = SPACERS_AFTER; } - shape(cpu_cells, gpu_cells, 2, hbf, font, false); + shape(cpu_cells, gpu_cells, 2, hbf, font, false, tc); if (G(num_glyphs)) { char glyph_name[128]; glyph_name[arraysz(glyph_name)-1] = 0; for (unsigned i = 0; i < G(num_glyphs); i++) { @@ -956,8 +970,8 @@ detect_spacer_strategy(hb_font_t *hbf, Font *font) { // If spacer_strategy is still default, check ### glyph to confirm strategy // https://github.com/kovidgoyal/kitty/issues/4721 if (font->spacer_strategy == SPACERS_BEFORE) { - cpu_cells[0].ch = '#'; cpu_cells[1].ch = '#'; cpu_cells[2].ch = '#'; - shape(cpu_cells, gpu_cells, arraysz(cpu_cells), hbf, font, false); + for (unsigned i = 0; i < arraysz(cpu_cells); i++) cell_set_char(&cpu_cells[i], '#'); + shape(cpu_cells, gpu_cells, arraysz(cpu_cells), hbf, font, false, tc); if (G(num_glyphs) > 1) { glyph_index glyph_id = G(info)[G(num_glyphs) - 1].codepoint; bool is_special = is_special_glyph(glyph_id, font, &G(current_cell_data)); @@ -1000,7 +1014,7 @@ static LigatureType *ligature_types = NULL; static size_t ligature_types_sz = 0; static void -group_iosevka(Font *font, hb_font_t *hbf) { +group_iosevka(Font *font, hb_font_t *hbf, const TextCache *tc) { // Group as per algorithm discussed in: https://github.com/be5invis/Iosevka/issues/1007 if (ligature_types_sz <= G(num_glyphs)) { ligature_types_sz = G(num_glyphs) + 16; @@ -1054,7 +1068,7 @@ group_iosevka(Font *font, hb_font_t *hbf) { } else { unsigned int num_cells_consumed = 0; while (num_codepoints_used_by_glyph && G(cell_idx) < G(num_cells)) { - unsigned int w = check_cell_consumed(&G(current_cell_data), G(last_cpu_cell)); + unsigned int w = check_cell_consumed(&G(current_cell_data), G(last_cpu_cell), tc); G(cell_idx) += w; num_cells_consumed += w; num_codepoints_used_by_glyph--; @@ -1067,7 +1081,7 @@ group_iosevka(Font *font, hb_font_t *hbf) { } static void -group_normal(Font *font, hb_font_t *hbf) { +group_normal(Font *font, hb_font_t *hbf, const TextCache *tc) { /* Now distribute the glyphs into groups of cells * Considerations to keep in mind: * Group sizes should be as small as possible for best performance @@ -1154,7 +1168,7 @@ group_normal(Font *font, hb_font_t *hbf) { } else { unsigned int num_cells_consumed = 0; while (num_codepoints_used_by_glyph && G(cell_idx) < G(num_cells)) { - unsigned int w = check_cell_consumed(&G(current_cell_data), G(last_cpu_cell)); + unsigned int w = check_cell_consumed(&G(current_cell_data), G(last_cpu_cell), tc); G(cell_idx) += w; num_cells_consumed += w; num_codepoints_used_by_glyph--; @@ -1177,12 +1191,12 @@ group_normal(Font *font, hb_font_t *hbf) { static void -shape_run(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, Font *font, bool disable_ligature) { +shape_run(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, Font *font, bool disable_ligature, const TextCache *tc) { hb_font_t *hbf = harfbuzz_font_for_face(font->face); - if (font->spacer_strategy == SPACER_STRATEGY_UNKNOWN) detect_spacer_strategy(hbf, font); - shape(first_cpu_cell, first_gpu_cell, num_cells, hbf, font, disable_ligature); - if (font->spacer_strategy == SPACERS_IOSEVKA) group_iosevka(font, hbf); - else group_normal(font, hbf); + if (font->spacer_strategy == SPACER_STRATEGY_UNKNOWN) detect_spacer_strategy(hbf, font, tc); + shape(first_cpu_cell, first_gpu_cell, num_cells, hbf, font, disable_ligature, tc); + if (font->spacer_strategy == SPACERS_IOSEVKA) group_iosevka(font, hbf, tc); + else group_normal(font, hbf, tc); #if 0 static char dbuf[1024]; // You can also generate this easily using hb-shape --show-extents --cluster-level=1 --shapers=ot /path/to/font/file text @@ -1228,7 +1242,7 @@ split_run_at_offset(index_type cursor_offset, index_type *left, index_type *righ static void -render_groups(FontGroup *fg, Font *font, bool center_glyph) { +render_groups(FontGroup *fg, Font *font, bool center_glyph, const TextCache *tc) { unsigned idx = 0; while (idx <= G(group_idx)) { Group *group = G(groups) + idx; @@ -1244,7 +1258,7 @@ render_groups(FontGroup *fg, Font *font, bool center_glyph) { global_glyph_render_scratch.sz = sz; } for (unsigned i = 0; i < group->num_glyphs; i++) global_glyph_render_scratch.glyphs[i] = G(info)[group->first_glyph_idx + i].codepoint; - render_group(fg, group->num_cells, group->num_glyphs, G(first_cpu_cell) + group->first_cell_idx, G(first_gpu_cell) + group->first_cell_idx, G(info) + group->first_glyph_idx, G(positions) + group->first_glyph_idx, font, global_glyph_render_scratch.glyphs, group->num_glyphs, center_glyph); + render_group(fg, group->num_cells, group->num_glyphs, G(first_cpu_cell) + group->first_cell_idx, G(first_gpu_cell) + group->first_cell_idx, G(info) + group->first_glyph_idx, G(positions) + group->first_glyph_idx, font, global_glyph_render_scratch.glyphs, group->num_glyphs, center_glyph, tc); } idx++; } @@ -1257,7 +1271,7 @@ test_shape(PyObject UNUSED *self, PyObject *args) { int index = 0; if(!PyArg_ParseTuple(args, "O!|zi", &Line_Type, &line, &path, &index)) return NULL; index_type num = 0; - while(num < line->xnum && line->cpu_cells[num].ch) num += line->gpu_cells[num].attrs.width; + while(num < line->xnum && cell_has_text(line->cpu_cells + num)) num += line->gpu_cells[num].attrs.width; PyObject *face = NULL; Font *font; if (!num_font_groups) { PyErr_SetString(PyExc_RuntimeError, "must create at least one font group first"); return NULL; } @@ -1271,7 +1285,7 @@ test_shape(PyObject UNUSED *self, PyObject *args) { FontGroup *fg = font_groups; font = fg->fonts + fg->medium_font_idx; } - shape_run(line->cpu_cells, line->gpu_cells, num, font, false); + shape_run(line->cpu_cells, line->gpu_cells, num, font, false, line->text_cache); PyObject *ans = PyList_New(0); unsigned int idx = 0; @@ -1292,35 +1306,35 @@ test_shape(PyObject UNUSED *self, PyObject *args) { #undef G static void -render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, ssize_t font_idx, bool pua_space_ligature, bool center_glyph, int cursor_offset, DisableLigature disable_ligature_strategy) { +render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, ssize_t font_idx, bool pua_space_ligature, bool center_glyph, int cursor_offset, DisableLigature disable_ligature_strategy, const TextCache *tc) { switch(font_idx) { default: - shape_run(first_cpu_cell, first_gpu_cell, num_cells, &fg->fonts[font_idx], disable_ligature_strategy == DISABLE_LIGATURES_ALWAYS); + shape_run(first_cpu_cell, first_gpu_cell, num_cells, &fg->fonts[font_idx], disable_ligature_strategy == DISABLE_LIGATURES_ALWAYS, tc); if (pua_space_ligature) collapse_pua_space_ligature(num_cells); else if (cursor_offset > -1) { // false if DISABLE_LIGATURES_NEVER index_type left, right; split_run_at_offset(cursor_offset, &left, &right); if (right > left) { if (left) { - shape_run(first_cpu_cell, first_gpu_cell, left, &fg->fonts[font_idx], false); - render_groups(fg, &fg->fonts[font_idx], center_glyph); + shape_run(first_cpu_cell, first_gpu_cell, left, &fg->fonts[font_idx], false, tc); + render_groups(fg, &fg->fonts[font_idx], center_glyph, tc); } - shape_run(first_cpu_cell + left, first_gpu_cell + left, right - left, &fg->fonts[font_idx], true); - render_groups(fg, &fg->fonts[font_idx], center_glyph); + shape_run(first_cpu_cell + left, first_gpu_cell + left, right - left, &fg->fonts[font_idx], true, tc); + render_groups(fg, &fg->fonts[font_idx], center_glyph, tc); if (right < num_cells) { - shape_run(first_cpu_cell + right, first_gpu_cell + right, num_cells - right, &fg->fonts[font_idx], false); - render_groups(fg, &fg->fonts[font_idx], center_glyph); + shape_run(first_cpu_cell + right, first_gpu_cell + right, num_cells - right, &fg->fonts[font_idx], false, tc); + render_groups(fg, &fg->fonts[font_idx], center_glyph, tc); } break; } } - render_groups(fg, &fg->fonts[font_idx], center_glyph); + render_groups(fg, &fg->fonts[font_idx], center_glyph, tc); break; case BLANK_FONT: while(num_cells--) { set_sprite(first_gpu_cell, 0, 0, 0); first_cpu_cell++; first_gpu_cell++; } break; case BOX_FONT: - while(num_cells--) { render_box_cell(fg, first_cpu_cell, first_gpu_cell); first_cpu_cell++; first_gpu_cell++; } + while(num_cells--) { render_box_cell(fg, first_cpu_cell, first_gpu_cell, tc); first_cpu_cell++; first_gpu_cell++; } break; case MISSING_FONT: while(num_cells--) { set_sprite(first_gpu_cell, MISSING_GLYPH, 0, 0); first_cpu_cell++; first_gpu_cell++; } @@ -1352,11 +1366,11 @@ cell_cap_for_codepoint(const char_type cp) { void -render_line(FONTS_DATA_HANDLE fg_, Line *line, index_type lnum, Cursor *cursor, DisableLigature disable_ligature_strategy) { +render_line(FONTS_DATA_HANDLE fg_, Line *line, index_type lnum, Cursor *cursor, DisableLigature disable_ligature_strategy, ListOfChars *lc) { #define RENDER if (run_font_idx != NO_FONT && i > first_cell_in_run) { \ int cursor_offset = -1; \ if (disable_ligature_at_cursor && first_cell_in_run <= cursor->x && cursor->x <= i) cursor_offset = cursor->x - first_cell_in_run; \ - render_run(fg, line->cpu_cells + first_cell_in_run, line->gpu_cells + first_cell_in_run, i - first_cell_in_run, run_font_idx, false, center_glyph, cursor_offset, disable_ligature_strategy); \ + render_run(fg, line->cpu_cells + first_cell_in_run, line->gpu_cells + first_cell_in_run, i - first_cell_in_run, run_font_idx, false, center_glyph, cursor_offset, disable_ligature_strategy, line->text_cache); \ } FontGroup *fg = (FontGroup*)fg_; ssize_t run_font_idx = NO_FONT; @@ -1368,30 +1382,32 @@ render_line(FONTS_DATA_HANDLE fg_, Line *line, index_type lnum, Cursor *cursor, if (prev_width == 2) { prev_width = 0; continue; } CPUCell *cpu_cell = line->cpu_cells + i; GPUCell *gpu_cell = line->gpu_cells + i; + text_in_cell(cpu_cell, line->text_cache, lc); bool is_main_font, is_emoji_presentation; - ssize_t cell_font_idx = font_for_cell(fg, cpu_cell, gpu_cell, &is_main_font, &is_emoji_presentation); + ssize_t cell_font_idx = font_for_cell(fg, cpu_cell, gpu_cell, &is_main_font, &is_emoji_presentation, line->text_cache, lc); + const char_type first_ch = lc->chars[0]; if ( cell_font_idx != MISSING_FONT && - ((!is_main_font && !is_emoji_presentation && is_symbol(cpu_cell->ch)) || (cell_font_idx != BOX_FONT && (is_private_use(cpu_cell->ch))) || is_non_emoji_dingbat(cpu_cell->ch)) + ((!is_main_font && !is_emoji_presentation && is_symbol(first_ch)) || (cell_font_idx != BOX_FONT && (is_private_use(first_ch))) || is_non_emoji_dingbat(first_ch)) ) { unsigned int desired_cells = 1; if (cell_font_idx > 0) { Font *font = (fg->fonts + cell_font_idx); - glyph_index glyph_id = glyph_id_for_codepoint(font->face, cpu_cell->ch); + glyph_index glyph_id = glyph_id_for_codepoint(font->face, first_ch); int width = get_glyph_width(font->face, glyph_id); desired_cells = (unsigned int)ceilf((float)width / fg->cell_width); } - desired_cells = MIN(desired_cells, cell_cap_for_codepoint(cpu_cell->ch)); + desired_cells = MIN(desired_cells, cell_cap_for_codepoint(first_ch)); unsigned int num_spaces = 0; while ( i + num_spaces + 1 < line->xnum - && (line->cpu_cells[i+num_spaces+1].ch == ' ' || line->cpu_cells[i+num_spaces+1].ch == 0x2002) // space or en-space + && (cell_is_char(line->cpu_cells + i + num_spaces + 1, ' ') || cell_is_char(line->cpu_cells + i + num_spaces + 1, 0x2002)) // space or en-space && num_spaces < MAX_NUM_EXTRA_GLYPHS_PUA && num_spaces + 1 < desired_cells - ) { + ) { num_spaces++; // We have a private use char followed by space(s), render it as a multi-cell ligature. GPUCell *space_cell = line->gpu_cells + i + num_spaces; @@ -1406,7 +1422,7 @@ render_line(FONTS_DATA_HANDLE fg_, Line *line, index_type lnum, Cursor *cursor, center_glyph = true; RENDER center_glyph = false; - render_run(fg, line->cpu_cells + i, line->gpu_cells + i, num_spaces + 1, cell_font_idx, true, center_glyph, -1, disable_ligature_strategy); + render_run(fg, line->cpu_cells + i, line->gpu_cells + i, num_spaces + 1, cell_font_idx, true, center_glyph, -1, disable_ligature_strategy, line->text_cache); run_font_idx = NO_FONT; first_cell_in_run = i + num_spaces + 1; prev_width = line->gpu_cells[i+num_spaces].attrs.width; @@ -1622,7 +1638,8 @@ test_render_line(PyObject UNUSED *self, PyObject *args) { PyObject *line; if (!PyArg_ParseTuple(args, "O!", &Line_Type, &line)) return NULL; if (!num_font_groups) { PyErr_SetString(PyExc_RuntimeError, "must create font group first"); return NULL; } - render_line((FONTS_DATA_HANDLE)font_groups, (Line*)line, 0, NULL, DISABLE_LIGATURES_NEVER); + RAII_ListOfChars(lc); + render_line((FONTS_DATA_HANDLE)font_groups, (Line*)line, 0, NULL, DISABLE_LIGATURES_NEVER, &lc); Py_RETURN_NONE; } @@ -1710,16 +1727,13 @@ get_fallback_font(PyObject UNUSED *self, PyObject *args) { PyObject *text; int bold, italic; if (!PyArg_ParseTuple(args, "Upp", &text, &bold, &italic)) return NULL; - CPUCell cpu_cell = {0}; GPUCell gpu_cell = {0}; - static Py_UCS4 char_buf[2 + arraysz(cpu_cell.cc_idx)]; - if (!PyUnicode_AsUCS4(text, char_buf, arraysz(char_buf), 1)) return NULL; - cpu_cell.ch = char_buf[0]; - for (unsigned i = 0; i + 1 < (unsigned) PyUnicode_GetLength(text) && i < arraysz(cpu_cell.cc_idx); i++) cpu_cell.cc_idx[i] = mark_for_codepoint(char_buf[i + 1]); + RAII_ListOfChars(lc); ensure_space_for_chars(&lc, PyUnicode_GetLength(text)); + if (!PyUnicode_AsUCS4(text, lc.chars, lc.count, 1)) return NULL; if (bold) gpu_cell.attrs.bold = true; if (italic) gpu_cell.attrs.italic = true; FontGroup *fg = font_groups; - ssize_t ans = fallback_font(fg, &cpu_cell, &gpu_cell); + ssize_t ans = fallback_font(fg, &gpu_cell, &lc); if (ans == MISSING_FONT) { PyErr_SetString(PyExc_ValueError, "No fallback font found"); return NULL; } if (ans < 0) { PyErr_SetString(PyExc_ValueError, "Too many fallback fonts"); return NULL; } return fg->fonts[ans].face; diff --git a/kitty/fonts.h b/kitty/fonts.h index dd89e4d54..78091c679 100644 --- a/kitty/fonts.h +++ b/kitty/fonts.h @@ -41,7 +41,7 @@ hb_font_t* harfbuzz_font_for_face(PyObject*); bool set_size_for_face(PyObject*, unsigned int, bool, FONTS_DATA_HANDLE); void cell_metrics(PyObject*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*); bool render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE, bool center_glyph); -PyObject* create_fallback_face(PyObject *base_face, CPUCell* cell, bool bold, bool italic, bool emoji_presentation, FONTS_DATA_HANDLE fg); +PyObject* create_fallback_face(PyObject *base_face, const ListOfChars *lc, bool bold, bool italic, bool emoji_presentation, FONTS_DATA_HANDLE fg); PyObject* specialize_font_descriptor(PyObject *base_descriptor, double, double, double); PyObject* face_from_path(const char *path, int index, FONTS_DATA_HANDLE); PyObject* face_from_descriptor(PyObject*, FONTS_DATA_HANDLE); @@ -51,7 +51,7 @@ const char* postscript_name_for_face(const PyObject*); void sprite_tracker_current_layout(FONTS_DATA_HANDLE data, unsigned int *x, unsigned int *y, unsigned int *z); void render_alpha_mask(const uint8_t *alpha_mask, pixel* dest, Region *src_rect, Region *dest_rect, size_t src_stride, size_t dest_stride, pixel color_rgb); -void render_line(FONTS_DATA_HANDLE, Line *line, index_type lnum, Cursor *cursor, DisableLigature); +void render_line(FONTS_DATA_HANDLE, Line *line, index_type lnum, Cursor *cursor, DisableLigature, ListOfChars*); void sprite_tracker_set_limits(size_t max_texture_size, size_t max_array_len); typedef void (*free_extra_data_func)(void*); StringCanvas render_simple_text_impl(PyObject *s, const char *text, unsigned int baseline); @@ -74,7 +74,7 @@ bool create_features_for_face(const char* psname, PyObject *features, FontFeatur PyObject* font_features_as_dict(const FontFeatures *font_features); bool -has_cell_text(bool(*has_codepoint)(const void*, char_type ch), const void* face, const CPUCell *cell, bool do_debug); +has_cell_text(bool(*has_codepoint)(const void*, char_type ch), const void* face, bool do_debug, const ListOfChars *lc); static inline void right_shift_canvas(pixel *canvas, size_t width, size_t height, size_t amt) { diff --git a/kitty/line-buf.c b/kitty/line-buf.c index 3035a83c1..f5d8b24e9 100644 --- a/kitty/line-buf.c +++ b/kitty/line-buf.c @@ -602,7 +602,7 @@ linebuf_rewrap(LineBuf *self, LineBuf *other, index_type *num_content_lines_befo first--; CPUCell *cells = cpu_lineptr(self, self->line_map[first]); for(i = 0; i < self->xnum; i++) { - if ((cells[i].ch) != BLANK_CHAR) { is_empty = false; break; } + if (cells[i].ch_or_idx || cells[i].ch_is_idx) { is_empty = false; break; } } } while(is_empty && first > 0); diff --git a/kitty/line.c b/kitty/line.c index e728eeac1..25ce85a36 100644 --- a/kitty/line.c +++ b/kitty/line.c @@ -12,6 +12,7 @@ #include "wcwidth-std.h" extern PyTypeObject Cursor_Type; +static_assert(sizeof(char_type) == sizeof(Py_UCS4), "Need to perform conversion to Py_UCS4"); static void dealloc(Line* self) { @@ -23,24 +24,35 @@ dealloc(Line* self) { Py_TYPE(self)->tp_free((PyObject*)self); } +static unsigned +text_in_cell_ansi(const CPUCell *c, TextCache *tc, ANSIBuf *output) { + if (c->ch_is_idx) return tc_chars_at_index_ansi(tc, c->ch_or_idx, output); + ensure_space_for(output, buf, output->buf[0], output->len + 1, capacity, 2048, false); + output->buf[output->len++] = c->ch_or_idx; + return 1; +} + + unsigned int line_length(Line *self) { index_type last = self->xnum - 1; for (index_type i = 0; i < self->xnum; i++) { - if ((self->cpu_cells[last - i].ch) != BLANK_CHAR) return self->xnum - i; + if (!cell_is_char(self->cpu_cells + last - i, BLANK_CHAR)) return self->xnum - i; } return 0; } PyObject* -cell_text(CPUCell *cell) { - PyObject *ans; - unsigned num = 1; - static Py_UCS4 buf[arraysz(cell->cc_idx) + 1]; - buf[0] = cell->ch; - for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) buf[num++] = codepoint_for_mark(cell->cc_idx[i]); - ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, num); - return ans; +cell_text(CPUCell *cell, TextCache *tc) { + if (cell->ch_is_idx) { + RAII_ListOfChars(lc); + tc_chars_at_index(tc, cell->ch_or_idx, &lc); + PyObject *ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, &lc.chars, lc.count); + return ans; + } else { + Py_UCS4 ch = cell->ch_or_idx; + return PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, &ch, 1); + } } // URL detection {{{ @@ -50,8 +62,21 @@ is_hostname_char(char_type ch) { return ch == '[' || ch == ']' || is_url_char(ch); } +static bool +is_hostname_lc(const ListOfChars *lc) { + for (size_t i = 0; i < lc->count; i++) if (!is_hostname_char(lc->chars[i])) return false; + return true; +} + +static bool +is_url_lc(const ListOfChars *lc) { + for (size_t i = 0; i < lc->count; i++) if (!is_url_char(lc->chars[i])) return false; + return true; +} + + static index_type -find_colon_slash(Line *self, index_type x, index_type limit) { +find_colon_slash(Line *self, index_type x, index_type limit, ListOfChars *lc) { // Find :// at or before x index_type pos = MIN(x, self->xnum - 1); enum URL_PARSER_STATES {ANY, FIRST_SLASH, SECOND_SLASH}; @@ -59,25 +84,26 @@ find_colon_slash(Line *self, index_type x, index_type limit) { limit = MAX(2u, limit); if (pos < limit) return 0; do { - char_type ch = self->cpu_cells[pos].ch; - if (!is_hostname_char(ch)) return false; + const CPUCell *c = self->cpu_cells + pos; + text_in_cell(c, self->text_cache, lc); + if (!is_hostname_lc(lc)) return false; if (pos == x) { - if (ch == ':') { - if (pos + 2 < self->xnum && self->cpu_cells[pos+1].ch == '/' && self->cpu_cells[pos + 2].ch == '/') state = SECOND_SLASH; - } else if (ch == '/') { - if (pos + 1 < self->xnum && self->cpu_cells[pos+1].ch == '/') state = FIRST_SLASH; + if (cell_is_char(c, ':')) { + if (pos + 2 < self->xnum && cell_is_char(self->cpu_cells + pos + 1, '/') && cell_is_char(self->cpu_cells + pos + 2, '/')) state = SECOND_SLASH; + } else if (cell_is_char(c, '/')) { + if (pos + 1 < self->xnum && cell_is_char(self->cpu_cells + pos + 1, '/')) state = FIRST_SLASH; } } switch(state) { case ANY: - if (ch == '/') state = FIRST_SLASH; + if (cell_is_char(c, '/')) state = FIRST_SLASH; break; case FIRST_SLASH: - state = ch == '/' ? SECOND_SLASH : ANY; + state = cell_is_char(c, '/') ? SECOND_SLASH : ANY; break; case SECOND_SLASH: - if (ch == ':') return pos; - state = ch == '/' ? SECOND_SLASH : ANY; + if (cell_is_char(c, ':')) return pos; + state = cell_is_char(c, '/') ? SECOND_SLASH : ANY; break; } pos--; @@ -90,7 +116,7 @@ prefix_matches(Line *self, index_type at, const char_type* prefix, index_type pr if (prefix_len > at) return false; index_type p, i; for (p = at - prefix_len, i = 0; i < prefix_len && p < self->xnum; i++, p++) { - if ((self->cpu_cells[p].ch) != prefix[i]) return false; + if (!cell_is_char(self->cpu_cells + p, prefix[i])) return false; } return i == prefix_len; } @@ -108,15 +134,18 @@ has_url_prefix_at(Line *self, index_type at, index_type min_prefix_len, index_ty #define MIN_URL_LEN 5 static bool -has_url_beyond_colon_slash(Line *self, index_type x) { +has_url_beyond_colon_slash(Line *self, index_type x, ListOfChars *lc) { unsigned num_of_slashes = 0; for (index_type i = x; i < MIN(x + MIN_URL_LEN + 3, self->xnum); i++) { - const char_type ch = self->cpu_cells[i].ch; + const CPUCell *c = self->cpu_cells + i; + text_in_cell(c, self->text_cache, lc); if (num_of_slashes < 3) { - if (!is_hostname_char(ch)) return false; - if (ch == '/') num_of_slashes++; + if (!is_hostname_lc(lc)) return false; + if (lc->count == 1 && lc->chars[0] == '/') num_of_slashes++; + } + else { + for (size_t n = 0; n < lc->count; n++) if (!is_url_char(lc->chars[n])) return false; } - else { if (!is_url_char(ch)) return false; } } return true; } @@ -127,34 +156,37 @@ line_url_start_at(Line *self, index_type x) { // known-prefix://url-chars. If no URL is found self->xnum is returned. if (x >= self->xnum || self->xnum <= MIN_URL_LEN + 3) return self->xnum; index_type ds_pos = 0, t; + RAII_ListOfChars(lc); // First look for :// ahead of x - ds_pos = find_colon_slash(self, x + OPT(url_prefixes).max_prefix_len + 3, x < 2 ? 0 : x - 2); - if (ds_pos != 0 && has_url_beyond_colon_slash(self, ds_pos)) { + ds_pos = find_colon_slash(self, x + OPT(url_prefixes).max_prefix_len + 3, x < 2 ? 0 : x - 2, &lc); + if (ds_pos != 0 && has_url_beyond_colon_slash(self, ds_pos, &lc)) { if (has_url_prefix_at(self, ds_pos, ds_pos > x ? ds_pos - x: 0, &t)) return t; } - ds_pos = find_colon_slash(self, x, 0); - if (ds_pos == 0 || self->xnum < ds_pos + MIN_URL_LEN + 3 || !has_url_beyond_colon_slash(self, ds_pos)) return self->xnum; + ds_pos = find_colon_slash(self, x, 0, &lc); + if (ds_pos == 0 || self->xnum < ds_pos + MIN_URL_LEN + 3 || !has_url_beyond_colon_slash(self, ds_pos, &lc)) return self->xnum; if (has_url_prefix_at(self, ds_pos, 0, &t)) return t; return self->xnum; } static bool -is_pos_ok_for_url(Line *self, index_type x, bool in_hostname, index_type last_hostname_char_pos) { +is_pos_ok_for_url(Line *self, index_type x, bool in_hostname, index_type last_hostname_char_pos, ListOfChars *lc) { if (x >= self->xnum) return false; - if (in_hostname && x <= last_hostname_char_pos) return is_hostname_char(self->cpu_cells[x].ch); - return is_url_char(self->cpu_cells[x].ch); + text_in_cell(self->cpu_cells + x, self->text_cache, lc); + if (in_hostname && x <= last_hostname_char_pos) return is_hostname_lc(lc); + return is_url_lc(lc); } index_type line_url_end_at(Line *self, index_type x, bool check_short, char_type sentinel, bool next_line_starts_with_url_chars, bool in_hostname, index_type last_hostname_char_pos) { index_type ans = x; if (x >= self->xnum || (check_short && self->xnum <= MIN_URL_LEN + 3)) return 0; -#define pos_ok(x) is_pos_ok_for_url(self, x, in_hostname, last_hostname_char_pos) - if (sentinel) { while (ans < self->xnum && self->cpu_cells[ans].ch != sentinel && pos_ok(ans)) ans++; } + RAII_ListOfChars(lc); +#define pos_ok(x) is_pos_ok_for_url(self, x, in_hostname, last_hostname_char_pos, &lc) + if (sentinel) { while (ans < self->xnum && !cell_is_char(self->cpu_cells + ans, sentinel) && pos_ok(ans)) ans++; } else { while (ans < self->xnum && pos_ok(ans)) ans++; } if (ans) ans--; if (ans < self->xnum - 1 || !next_line_starts_with_url_chars) { - while (ans > x && can_strip_from_end_of_url(self->cpu_cells[ans].ch)) ans--; + while (ans > x && !self->cpu_cells[ans].ch_is_idx && can_strip_from_end_of_url(self->cpu_cells[ans].ch_or_idx)) ans--; } #undef pos_ok return ans; @@ -162,8 +194,10 @@ line_url_end_at(Line *self, index_type x, bool check_short, char_type sentinel, bool line_startswith_url_chars(Line *self, bool in_hostname) { - if (in_hostname) return is_hostname_char(self->cpu_cells[0].ch); - return is_url_char(self->cpu_cells[0].ch); + RAII_ListOfChars(lc); + text_in_cell(self->cpu_cells, self->text_cache, &lc); + if (in_hostname) return is_hostname_lc(&lc); + return is_url_lc(&lc); } @@ -188,54 +222,41 @@ static PyObject* text_at(Line* self, Py_ssize_t xval) { #define text_at_doc "[x] -> Return the text in the specified cell" if ((unsigned)xval >= self->xnum) { PyErr_SetString(PyExc_IndexError, "Column number out of bounds"); return NULL; } - return cell_text(self->cpu_cells + xval); + return cell_text(self->cpu_cells + xval, self->text_cache); } -size_t -cell_as_unicode(CPUCell *cell, bool include_cc, Py_UCS4 *buf, char_type zero_char) { +static size_t +cell_as_unicode(ListOfChars *lc, bool include_cc, Py_UCS4 *buf, char_type zero_char) { size_t n = 1; - buf[0] = cell->ch ? cell->ch : zero_char; - if (include_cc) { - for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) buf[n++] = codepoint_for_mark(cell->cc_idx[i]); - } + buf[0] = lc->chars[0] ? lc->chars[0] : zero_char; + if (include_cc && lc->count > 1) memcpy(buf + 1, lc->chars + 1, lc->count - 1); return n; } size_t -cell_as_unicode_for_fallback(CPUCell *cell, Py_UCS4 *buf) { +cell_as_unicode_for_fallback(const ListOfChars *lc, Py_UCS4 *buf) { size_t n = 1; - buf[0] = cell->ch ? cell->ch : ' '; + buf[0] = lc->chars[0] ? lc->chars[0] : ' '; if (buf[0] != '\t') { - for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) { - if (cell->cc_idx[i] != VS15 && cell->cc_idx[i] != VS16) buf[n++] = codepoint_for_mark(cell->cc_idx[i]); + for (unsigned i = 1; i < lc->count; i++) { + if (lc->chars[i] != VS15 && lc->chars[i] != VS16) buf[n++] = lc->chars[i]; } } else buf[0] = ' '; return n; } size_t -cell_as_utf8(CPUCell *cell, bool include_cc, char *buf, char_type zero_char) { - char_type ch = cell->ch ? cell->ch : zero_char; - if (ch == '\t') { include_cc = false; } - size_t n = encode_utf8(ch, buf); - if (include_cc) { - for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) n += encode_utf8(codepoint_for_mark(cell->cc_idx[i]), buf + n); - } - buf[n] = 0; - return n; -} - -size_t -cell_as_utf8_for_fallback(CPUCell *cell, char *buf) { - char_type ch = cell->ch ? cell->ch : ' '; +cell_as_utf8_for_fallback(CPUCell *cell, TextCache *tc, char *buf) { + RAII_ListOfChars(lc); + text_in_cell(cell, tc, &lc); + char_type ch = lc.chars[0] ? lc.chars[0] : ' '; bool include_cc = true; if (ch == '\t') { ch = ' '; include_cc = false; } size_t n = encode_utf8(ch, buf); if (include_cc) { - for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) { - if (cell->cc_idx[i] != VS15 && cell->cc_idx[i] != VS16) { - n += encode_utf8(codepoint_for_mark(cell->cc_idx[i]), buf + n); - } + for (unsigned i = 1; i < lc.count; i++) { + char_type ch = lc.chars[i]; + if (ch != VS15 && ch != VS16) n += encode_utf8(ch, buf + n); } } buf[n] = 0; @@ -243,33 +264,32 @@ cell_as_utf8_for_fallback(CPUCell *cell, char *buf) { } - PyObject* unicode_in_range(const Line *self, const index_type start, const index_type limit, const bool include_cc, const bool add_trailing_newline, const bool skip_zero_cells) { size_t n = 0; static Py_UCS4 buf[4096]; + RAII_ListOfChars(lc); char_type previous_width = 0; - for(index_type i = start; i < limit && n < arraysz(buf) - 2 - arraysz(self->cpu_cells->cc_idx); i++) { - char_type ch = self->cpu_cells[i].ch; - if (ch == 0) { + for(index_type i = start; i < limit; i++) { + text_in_cell(self->cpu_cells + i, self->text_cache, &lc); + if (!lc.chars[0]) { if (previous_width == 2) { previous_width = 0; continue; }; if (skip_zero_cells) continue; } - if (ch == '\t') { + if (lc.count + n >= arraysz(buf)) break; + if (lc.chars[0] == '\t') { buf[n++] = '\t'; - unsigned num_cells_to_skip_for_tab = self->cpu_cells[i].cc_idx[0]; - while (num_cells_to_skip_for_tab && i + 1 < limit && self->cpu_cells[i+1].ch == ' ') { + unsigned num_cells_to_skip_for_tab = lc.count > 1 ? lc.chars[1] : 0; + while (num_cells_to_skip_for_tab && i + 1 < limit && cell_is_char(self->cpu_cells+i+1, ' ')) { i++; num_cells_to_skip_for_tab--; } } else { - n += cell_as_unicode(self->cpu_cells + i, include_cc, buf + n, ' '); + n += cell_as_unicode(&lc, include_cc, buf + n, ' '); } previous_width = self->gpu_cells[i].attrs.width; } - if (add_trailing_newline && !self->gpu_cells[self->xnum-1].attrs.next_char_was_wrapped && n < arraysz(buf)) { - buf[n++] = '\n'; - } + if (add_trailing_newline && !self->gpu_cells[self->xnum-1].attrs.next_char_was_wrapped && n < arraysz(buf)) buf[n++] = '\n'; return PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, n); } @@ -331,7 +351,7 @@ write_mark(const char *mark, ANSIBuf *output) { bool line_as_ansi(Line *self, ANSIBuf *output, const GPUCell** prev_cell, index_type start_at, index_type stop_before, char_type prefix_char) { -#define ENSURE_SPACE(extra) ensure_space_for(output, buf, Py_UCS4, output->len + extra, capacity, 2048, false); +#define ENSURE_SPACE(extra) ensure_space_for(output, buf, output->buf[0], output->len + extra, capacity, 2048, false); #define WRITE_SGR(val) { ENSURE_SPACE(128); escape_code_written = true; write_sgr(val, output); } #define WRITE_CH(val) { ENSURE_SPACE(1); output->buf[output->len++] = val; } #define WRITE_HYPERLINK(val) { ENSURE_SPACE(2256); escape_code_written = true; write_hyperlink(val, output); } @@ -363,10 +383,10 @@ line_as_ansi(Line *self, ANSIBuf *output, const GPUCell** prev_cell, index_type const CellAttrs mask_for_sgr = {.val=SGR_MASK}; for (index_type pos=start_at; pos < limit; pos++) { - char_type ch = self->cpu_cells[pos].ch; - if (ch == 0) { - if (previous_width == 2) { previous_width = 0; continue; } - ch = ' '; + unsigned n = text_in_cell_ansi(self->cpu_cells + pos, self->text_cache, output); + if (output->buf[output->len - n] == 0) { + if (previous_width == 2) { previous_width = 0; output->len -= n; continue; } + output->buf[output->len - n] = ' '; } if (output->hyperlink_pool) { hyperlink_id_type hid = self->cpu_cells[pos].hyperlink_id; @@ -384,15 +404,14 @@ line_as_ansi(Line *self, ANSIBuf *output, const GPUCell** prev_cell, index_type if (*sgr) WRITE_SGR(sgr); } *prev_cell = cell; - WRITE_CH(ch); - if (ch == '\t') { - unsigned num_cells_to_skip_for_tab = self->cpu_cells[pos].cc_idx[0]; - while (num_cells_to_skip_for_tab && pos + 1 < limit && self->cpu_cells[pos+1].ch == ' ') { - num_cells_to_skip_for_tab--; pos++; + if (output->buf[output->len - n] == '\t') { + unsigned num_cells_to_skip_for_tab = 0; + if (n > 1) { + num_cells_to_skip_for_tab = output->buf[output->len - n + 1]; + output->len -= n - 1; } - } else { - for(unsigned c = 0; c < arraysz(self->cpu_cells[pos].cc_idx) && self->cpu_cells[pos].cc_idx[c]; c++) { - WRITE_CH(codepoint_for_mark(self->cpu_cells[pos].cc_idx[c])); + while (num_cells_to_skip_for_tab && pos + 1 < limit && cell_is_char(self->cpu_cells + pos + 1, ' ')) { + num_cells_to_skip_for_tab--; pos++; } } previous_width = cell->attrs.width; @@ -448,17 +467,19 @@ width(Line *self, PyObject *val) { return PyLong_FromUnsignedLong((unsigned long) (self->gpu_cells[x].attrs.width)); } -void -line_add_combining_char(CPUCell *cpu_cells, GPUCell *gpu_cells, uint32_t ch, unsigned int x) { +bool +line_add_combining_char(CPUCell *cpu_cells, GPUCell *gpu_cells, TextCache *tc, ListOfChars *lc, uint32_t ch, unsigned int x) { CPUCell *cell = cpu_cells + x; - if (!cell->ch) { - 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 + if (!cell->ch_or_idx && !cell->ch_is_idx) { + if (x > 0 && (gpu_cells[x-1].attrs.width) == 2 && cell_has_text(cpu_cells + x - 1)) cell = cpu_cells + x - 1; + else return false; // don't allow adding combining chars to a null cell } - for (unsigned i = 0; i < arraysz(cell->cc_idx); i++) { - if (!cell->cc_idx[i]) { cell->cc_idx[i] = mark_for_codepoint(ch); return; } - } - cell->cc_idx[arraysz(cell->cc_idx) - 1] = mark_for_codepoint(ch); + text_in_cell(cell, tc, lc); + ensure_space_for_chars(lc, lc->count + 1); + lc->chars[lc->count++] = ch; + cell->ch_or_idx = tc_get_or_insert_chars(tc, lc); + cell->ch_is_idx = true; + return true; } static PyObject* @@ -471,7 +492,8 @@ add_combining_char(Line* self, PyObject *args) { PyErr_SetString(PyExc_ValueError, "Column index out of bounds"); return NULL; } - line_add_combining_char(self->cpu_cells, self->gpu_cells, new_char, x); + RAII_ListOfChars(lc); + line_add_combining_char(self->cpu_cells, self->gpu_cells, self->text_cache, &lc, new_char, x); Py_RETURN_NONE; } @@ -502,13 +524,12 @@ set_text(Line* self, PyObject *args) { color_type dfg = cursor->decoration_fg & COL_MASK; for (index_type i = cursor->x; offset < limit && i < self->xnum; i++, offset++) { - self->cpu_cells[i].ch = (PyUnicode_READ(kind, buf, offset)); - self->cpu_cells[i].hyperlink_id = 0; + self->cpu_cells[i].val = 0; + self->cpu_cells[i].ch_or_idx = PyUnicode_READ(kind, buf, offset); self->gpu_cells[i].attrs = attrs; self->gpu_cells[i].fg = fg; self->gpu_cells[i].bg = bg; self->gpu_cells[i].decoration_fg = dfg; - memset(self->cpu_cells[i].cc_idx, 0, sizeof(self->cpu_cells[i].cc_idx)); } Py_RETURN_NONE; @@ -537,7 +558,7 @@ cursor_from(Line* self, PyObject *args) { void line_clear_text(Line *self, unsigned int at, unsigned int num, char_type ch) { const uint16_t width = ch ? 1 : 0; - const CPUCell cc = {.ch=ch}; + const CPUCell cc = {.ch_or_idx=ch}; if (at + num > self->xnum) num = self->xnum > at ? self->xnum - at : 0; memset_array(self->cpu_cells + at, cc, num); for (index_type i = at; i < at + num; i++) self->gpu_cells[i].attrs.width = width; @@ -584,15 +605,16 @@ apply_cursor(Line* self, PyObject *args) { Py_RETURN_NONE; } -void line_right_shift(Line *self, unsigned int at, unsigned int num) { +void +line_right_shift(Line *self, unsigned int at, unsigned int num) { for(index_type i = self->xnum - 1; i >= at + num; i--) { COPY_SELF_CELL(i - num, i) } // Check if a wide character was split at the right edge if (self->gpu_cells[self->xnum - 1].attrs.width != 1) { - self->cpu_cells[self->xnum - 1].ch = BLANK_CHAR; + self->cpu_cells[self->xnum - 1].val = 0; self->cpu_cells[self->xnum - 1].hyperlink_id = 0; - self->gpu_cells[self->xnum - 1].attrs = (CellAttrs){.width=BLANK_CHAR ? 1 : 0}; + self->gpu_cells[self->xnum - 1].attrs = (CellAttrs){0}; clear_sprite_position(self->gpu_cells[self->xnum - 1]); } } @@ -654,11 +676,18 @@ colors_for_cell(Line *self, const ColorProfile *cp, index_type *x, color_type *f char_type line_get_char(Line *self, index_type at) { - char_type ch = self->cpu_cells[at].ch; - if (!ch && at > 0 && (self->gpu_cells[at-1].attrs.width) > 1) ch = self->cpu_cells[at-1].ch; - return ch; + if (self->cpu_cells[at].ch_is_idx) { + RAII_ListOfChars(lc); + text_in_cell(self->cpu_cells + at, self->text_cache, &lc); + return lc.chars[0]; + } else { + char_type ch = self->cpu_cells[at].ch_or_idx; + if (!ch && at > 0 && (self->gpu_cells[at-1].attrs.width) > 1) ch = line_get_char(self, at - 1); + return ch; + } } + void line_set_char(Line *self, unsigned int at, uint32_t ch, unsigned int width, Cursor *cursor, hyperlink_id_type hyperlink_id) { GPUCell *g = self->gpu_cells + at; @@ -670,13 +699,12 @@ line_set_char(Line *self, unsigned int at, uint32_t ch, unsigned int width, Curs g->bg = cursor->bg & COL_MASK; g->decoration_fg = cursor->decoration_fg & COL_MASK; } - self->cpu_cells[at].ch = ch; + cell_set_char(self->cpu_cells + at, ch); self->cpu_cells[at].hyperlink_id = hyperlink_id; if (OPT(underline_hyperlinks) == UNDERLINE_ALWAYS && hyperlink_id) { g->decoration_fg = ((OPT(url_color) & COL_MASK) << 8) | 2; g->attrs.decoration = OPT(url_style); } - memset(self->cpu_cells[at].cc_idx, 0, sizeof(self->cpu_cells[at].cc_idx)); } static PyObject* @@ -802,21 +830,20 @@ apply_mark(Line *line, const uint16_t mark, index_type *cell_pos, unsigned int * index_type x = *cell_pos; MARK; (*match_pos)++; - if (line->cpu_cells[x].ch) { - if (line->cpu_cells[x].ch == '\t') { - unsigned num_cells_to_skip_for_tab = line->cpu_cells[x].cc_idx[0]; - while (num_cells_to_skip_for_tab && x + 1 < line->xnum && line->cpu_cells[x+1].ch == ' ') { + RAII_ListOfChars(lc); text_in_cell(line->cpu_cells + x, line->text_cache, &lc); + if (lc.chars[0]) { + if (lc.chars[0] == '\t') { + unsigned num_cells_to_skip_for_tab = lc.count > 1 ? lc.chars[1] : 0; + while (num_cells_to_skip_for_tab && x + 1 < line->xnum && cell_is_char(line->cpu_cells+x+1, ' ')) { x++; num_cells_to_skip_for_tab--; MARK; } - } else if ((line->gpu_cells[x].attrs.width) > 1 && x + 1 < line->xnum && !line->cpu_cells[x+1].ch) { + } else if ((line->gpu_cells[x].attrs.width) > 1 && x + 1 < line->xnum && !cell_has_text(line->cpu_cells+x+1)) { x++; MARK; } else { - for (index_type i = 0; i < arraysz(line->cpu_cells[x].cc_idx); i++) { - if (line->cpu_cells[x].cc_idx[i]) (*match_pos)++; - } + *match_pos += lc.count - 1; } } *cell_pos = x + 1; diff --git a/kitty/line.h b/kitty/line.h index 04d688576..f1651521d 100644 --- a/kitty/line.h +++ b/kitty/line.h @@ -28,6 +28,10 @@ typedef union CellAttrs { #define DECORATION_MASK (7u) #define NUM_UNDERLINE_STYLES (5u) #define SGR_MASK (~(((CellAttrs){.width=WIDTH_MASK, .mark=MARK_MASK, .next_char_was_wrapped=1}).val)) +// Text presentation selector +#define VS15 0xfe0e +// Emoji presentation selector +#define VS16 0xfe0f typedef struct { color_type fg, bg, decoration_fg; @@ -36,13 +40,16 @@ typedef struct { } GPUCell; static_assert(sizeof(GPUCell) == 20, "Fix the ordering of GPUCell"); -typedef struct { - char_type ch; - hyperlink_id_type hyperlink_id; - combining_type cc_idx[3]; +typedef union CPUCell { + struct { + char_type ch_or_idx: sizeof(char_type) * 8; + hyperlink_id_type hyperlink_id: sizeof(hyperlink_id_type) * 8; + bool ch_is_idx: 1; + uint16_t : 15; + }; + uint64_t val; } CPUCell; -static_assert(sizeof(CPUCell) == 12, "Fix the ordering of CPUCell"); - +static_assert(sizeof(CPUCell) == sizeof(uint64_t), "Fix the ordering of CPUCell"); typedef union LineAttrs { struct { @@ -69,6 +76,37 @@ typedef struct { Line* alloc_line(TextCache *text_cache); void apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, int *params, unsigned int count, bool is_group); const char* cell_as_sgr(const GPUCell *, const GPUCell *); +static inline bool cell_has_text(const CPUCell *c) { return c->ch_is_idx || c->ch_or_idx; } +static inline void cell_set_char(CPUCell *c, char_type ch) { c->ch_is_idx = false; c->ch_or_idx = ch; } +static inline bool cell_is_char(const CPUCell *c, char_type ch) { return !c->ch_is_idx && c->ch_or_idx == ch; } +static inline unsigned num_codepoints_in_cell(const CPUCell *c, const TextCache *tc) { + return c->ch_is_idx ? tc_num_codepoints(tc, c->ch_or_idx) : (c->ch_or_idx ? 1 : 0); +} + +static inline void +text_in_cell(const CPUCell *c, const TextCache *tc, ListOfChars *ans) { + if (c->ch_is_idx) tc_chars_at_index(tc, c->ch_or_idx, ans); + else { + ans->count = 1; + ans->chars[0] = c->ch_or_idx; + } +} + +static inline void +cell_set_chars(CPUCell *c, TextCache *tc, const ListOfChars *lc) { + if (lc->count <= 1) cell_set_char(c, lc->chars[0]); + else { + c->ch_or_idx = tc_get_or_insert_chars(tc, lc); + c->ch_is_idx = true; + } +} + +static inline char_type +cell_first_char(const CPUCell *c, const TextCache *tc) { + if (c->ch_is_idx) return tc_first_char_at_index(tc, c->ch_or_idx); + return c->ch_or_idx; +} + static inline CellAttrs cursor_to_attrs(const Cursor *c, const uint16_t width) { diff --git a/kitty/lineops.h b/kitty/lineops.h index f0077c7f0..c6201c0be 100644 --- a/kitty/lineops.h +++ b/kitty/lineops.h @@ -32,8 +32,8 @@ static inline void clear_chars_in_line(CPUCell *cpu_cells, GPUCell *gpu_cells, index_type xnum, char_type ch) { // Clear only the char part of each cell, the rest must have been cleared by a memset or similar if (ch) { - const CellAttrs empty = {.width=1}; - for (index_type i = 0; i < xnum; i++) { cpu_cells[i].ch = ch; cpu_cells[i].hyperlink_id = 0; gpu_cells[i].attrs = empty; } + static const CellAttrs empty = {.width=1}; + for (index_type i = 0; i < xnum; i++) { cpu_cells[i].val = 0; cpu_cells[i].ch_or_idx = ch; gpu_cells[i].attrs = empty; } } } @@ -41,7 +41,8 @@ static inline index_type xlimit_for_line(const Line *line) { index_type xlimit = line->xnum; if (BLANK_CHAR == 0) { - while (xlimit > 0 && (line->cpu_cells[xlimit - 1].ch) == BLANK_CHAR) xlimit--; + const CPUCell *c; + while (xlimit > 0 && !((c = line->cpu_cells + xlimit - 1)->ch_is_idx) && c->ch_or_idx == BLANK_CHAR) xlimit--; if (xlimit < line->xnum && line->gpu_cells[xlimit > 0 ? xlimit - 1 : xlimit].attrs.width == 2) xlimit++; } return xlimit; @@ -67,8 +68,7 @@ left_shift_line(Line *line, index_type at, index_type num) { const CellAttrs empty = {.width=1}; const CellAttrs zero = {{0}}; if (at < line->xnum && line->gpu_cells[at].attrs.width != 1) { - line->cpu_cells[at].ch = BLANK_CHAR; - line->cpu_cells[at].hyperlink_id = 0; + line->cpu_cells[at].val = 0; line->gpu_cells[at].attrs = BLANK_CHAR ? empty : zero; clear_sprite_position(line->gpu_cells[at]); } @@ -77,7 +77,7 @@ left_shift_line(Line *line, index_type at, index_type num) { static inline bool line_is_empty(const Line *line) { for (index_type i = 0; i < line->xnum; i++) { - if (line->cpu_cells[i].ch != BLANK_CHAR) return false; + if (line->cpu_cells[i].ch_is_idx || line->cpu_cells[i].ch_or_idx != BLANK_CHAR) return false; } return true; } @@ -88,16 +88,14 @@ void line_apply_cursor(Line *self, const Cursor *cursor, unsigned int at, unsign 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(CPUCell *, GPUCell *, uint32_t , unsigned int ); +bool line_add_combining_char(CPUCell *, GPUCell *, TextCache*, ListOfChars*, 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, index_type); bool line_startswith_url_chars(Line*, bool); bool line_as_ansi(Line *self, ANSIBuf *output, const GPUCell**, index_type start_at, index_type stop_before, char_type prefix_char) __attribute__((nonnull)); unsigned int line_length(Line *self); -size_t cell_as_unicode(CPUCell *cell, bool include_cc, Py_UCS4 *buf, char_type); -size_t cell_as_unicode_for_fallback(CPUCell *cell, Py_UCS4 *buf); -size_t cell_as_utf8(CPUCell *cell, bool include_cc, char *buf, char_type); -size_t cell_as_utf8_for_fallback(CPUCell *cell, char *buf); +size_t cell_as_unicode_for_fallback(const ListOfChars *lc, Py_UCS4 *buf); +size_t cell_as_utf8_for_fallback(CPUCell *cell, TextCache *tc, char *buf); PyObject* unicode_in_range(const Line *self, const index_type start, const index_type limit, const bool include_cc, const bool add_trailing_newline, const bool skip_zero_cells); PyObject* line_as_unicode(Line *, bool); diff --git a/kitty/rewrap.h b/kitty/rewrap.h index 696ea6619..f367338e9 100644 --- a/kitty/rewrap.h +++ b/kitty/rewrap.h @@ -67,7 +67,7 @@ rewrap_inner(BufType *src, BufType *dest, const index_type src_limit, HistoryBuf src_x_limit = src->xnum; if (!src_line_is_continued) { // Trim trailing blanks since there is a hard line break at the end of this line - while(src_x_limit && (src->line->cpu_cells[src_x_limit - 1].ch) == BLANK_CHAR) src_x_limit--; + while(src_x_limit && src->line->cpu_cells[src_x_limit - 1].ch_or_idx == BLANK_CHAR && !src->line->cpu_cells[src_x_limit - 1].ch_is_idx) src_x_limit--; } else { src->line->gpu_cells[src->xnum-1].attrs.next_char_was_wrapped = false; } diff --git a/kitty/screen.c b/kitty/screen.c index 27974278e..4bf714885 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -136,10 +136,11 @@ new_screen_object(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { self->grman = self->main_grman; self->disable_ligatures = OPT(disable_ligatures); self->main_tabstops = PyMem_Calloc(2 * self->columns, sizeof(bool)); + self->lc = alloc_list_of_chars(); if ( self->cursor == NULL || self->main_linebuf == NULL || self->alt_linebuf == NULL || self->main_tabstops == NULL || self->historybuf == NULL || self->main_grman == NULL || - self->alt_grman == NULL || self->color_profile == NULL + self->alt_grman == NULL || self->color_profile == NULL || self->lc == NULL ) { Py_CLEAR(self); return NULL; } @@ -335,7 +336,7 @@ found: linebuf_init_line(self->main_linebuf, y); // this is needed because screen_resize() checks to see if the cursor is beyond the content, // so insert some fake content - self->main_linebuf->line->cpu_cells[0].ch = ' '; + cell_set_char(self->main_linebuf->line->cpu_cells, ' '); if (y < (int)self->cursor->y) (*num_of_prompt_lines_above_cursor)++; } } @@ -346,10 +347,10 @@ static bool preserve_blank_output_start_line(Cursor *cursor, LineBuf *linebuf) { if (cursor->x == 0 && cursor->y < linebuf->ynum && !linebuf->line_attrs[cursor->y].is_continued) { linebuf_init_line(linebuf, cursor->y); - if (!linebuf->line->cpu_cells[0].ch) { + if (!cell_has_text(linebuf->line->cpu_cells)) { // we have a blank output start line, we need it to be preserved by // reflow, so insert a dummy char - linebuf->line->cpu_cells[cursor->x++].ch = '<'; + cell_set_char(linebuf->line->cpu_cells + cursor->x++, '<'); return true; } } @@ -360,7 +361,7 @@ static void remove_blank_output_line_reservation_marker(Cursor *cursor, LineBuf *linebuf) { if (cursor->y < linebuf->ynum) { linebuf_init_line(linebuf, cursor->y); - linebuf->line->cpu_cells[0].ch = 0; + cell_set_char(linebuf->line->cpu_cells, 0); cursor->x = 0; } } @@ -529,6 +530,7 @@ dealloc(Screen* self) { free_hyperlink_pool(self->hyperlink_pool); free(self->as_ansi_buf.buf); free(self->last_rendered_window_char.canvas); + if (self->lc) { cleanup_list_of_chars(self->lc); free(self->lc); self->lc = NULL; } Py_TYPE(self)->tp_free((PyObject*)self); } // }}} @@ -643,8 +645,9 @@ draw_second_flag_codepoint(Screen *self, char_type ch) { 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(cp, gp, ch, xpos); + text_in_cell(cell, self->text_cache, self->lc); + if (self->lc->count != 1 || !is_flag_pair(self->lc->chars[0], ch)) return false; + line_add_combining_char(cp, gp, self->text_cache, self->lc, ch, xpos); return true; } @@ -670,11 +673,12 @@ draw_combining_char(Screen *self, text_loop_state *s, char_type ch) { if (has_prev_char) { 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 + bool added = line_add_combining_char(cp, gp, self->text_cache, self->lc, ch, xpos); + unsigned base_pos = self->lc->count - (added ? 2 : 1); + if (ch == VS16) { // emoji presentation variation marker makes default text presentation emoji (narrow emoji) into wide emoji 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 (gpu_cell->attrs.width != 2 && added && self->lc->chars[base_pos + 1] == VS16 && is_emoji_presentation_base(self->lc->chars[base_pos])) { gpu_cell->attrs.width = 2; if (xpos + 1 < self->columns) { zero_cells(s, cp + xpos + 1, gp + xpos + 1); @@ -682,13 +686,13 @@ draw_combining_char(Screen *self, text_loop_state *s, char_type ch) { self->cursor->x++; } else move_widened_char(self, s, cpu_cell, gpu_cell, xpos, ypos); } - } else if (ch == 0xfe0e) { + } else if (ch == VS15) { CPUCell *cpu_cell = cp + xpos; GPUCell *gpu_cell = gp + xpos; - if (gpu_cell->attrs.width == 0 && cpu_cell->ch == 0 && xpos > 0) { + if (gpu_cell->attrs.width == 0 && !cell_has_text(cpu_cell) && xpos > 0) { cpu_cell--; gpu_cell--; } - if (gpu_cell->attrs.width == 2 && cpu_cell->cc_idx[0] == VS15 && is_emoji_presentation_base(cpu_cell->ch)) { + if (gpu_cell->attrs.width == 2 && added && self->lc->chars[base_pos + 1] == VS15 && is_emoji_presentation_base(self->lc->chars[base_pos])) { gpu_cell->attrs.width = 1; self->cursor->x--; } @@ -716,7 +720,7 @@ cursor_on_wide_char_trailer(Screen *self, text_loop_state *s) { static void move_cursor_off_wide_char_trailer(Screen *self, text_loop_state *s) { zero_cells(s, s->cp + self->cursor->x - 1, s->gp + self->cursor->x - 1); - s->cp[self->cursor->x-1].ch = ' '; + cell_set_char(&s->cp[self->cursor->x-1], ' '); zero_cells(s, s->cp + self->cursor->x, s->gp + self->cursor->x); } @@ -828,7 +832,7 @@ draw_text_loop(Screen *self, const uint32_t *chars, size_t num_chars, text_loop_ s->image_placeholder_marked = true; } zero_cells(s, s->cp + self->cursor->x, s->gp + self->cursor->x); - s->cp[self->cursor->x].ch = ch; + cell_set_char(&s->cp[self->cursor->x], ch); self->cursor->x++; if (char_width == 2) { s->gp[self->cursor->x-1].attrs.width = 2; @@ -1449,15 +1453,15 @@ screen_tab(Screen *self) { bool ok = true; for (combining_type i = 0; i < diff; i++) { CPUCell *c = cpu_cell + i; - if (c->ch != ' ' && c->ch != 0) { ok = false; break; } + if (cell_has_text(c) && !cell_is_char(c, ' ')) { ok = false; break; } } if (ok) { for (combining_type i = 0; i < diff; i++) { CPUCell *c = cpu_cell + i; - c->ch = ' '; zero_at_ptr_count(c->cc_idx, arraysz(c->cc_idx)); + cell_set_char(c, ' '); } - cpu_cell->ch = '\t'; - cpu_cell->cc_idx[0] = diff; + self->lc->count = 2; self->lc->chars[0] = '\t'; self->lc->chars[1] = diff; + cell_set_chars(cpu_cell, self->text_cache, self->lc); } } self->cursor->x = found; @@ -2618,8 +2622,7 @@ screen_has_marker(Screen *self) { return self->marker != NULL; } -static uint32_t diacritic_to_rowcolumn(combining_type m) { - char_type c = codepoint_for_mark(m); +static uint32_t diacritic_to_rowcolumn(char_type c) { return diacritic_to_num(c); } @@ -2656,21 +2659,22 @@ screen_render_line_graphics(Screen *self, Line *line, int32_t row) { uint32_t cur_img_id_higher8bits = 0; uint32_t cur_img_row = 0; uint32_t cur_img_col = 0; - if (cpu_cell->ch == IMAGE_PLACEHOLDER_CHAR) { + if (cell_first_char(cpu_cell, self->text_cache) == IMAGE_PLACEHOLDER_CHAR) { line->attrs.has_image_placeholders = true; // The lower 24 bits of the image id are encoded in the foreground // color, and the placement id is (optionally) in the underline color. cur_img_id_lower24bits = color_to_id(gpu_cell->fg); cur_placement_id = color_to_id(gpu_cell->decoration_fg); + text_in_cell(cpu_cell, self->text_cache, self->lc); // If the char has diacritics, use them as row and column indices. - if (cpu_cell->cc_idx[0]) - cur_img_row = diacritic_to_rowcolumn(cpu_cell->cc_idx[0]); - if (cpu_cell->cc_idx[1]) - cur_img_col = diacritic_to_rowcolumn(cpu_cell->cc_idx[1]); + if (self->lc->count > 1 && self->lc->chars[1]) + cur_img_row = diacritic_to_rowcolumn(self->lc->chars[1]); + if (self->lc->count > 2 && self->lc->chars[2]) + cur_img_col = diacritic_to_rowcolumn(self->lc->chars[2]); // The third diacritic is used to encode the higher 8 bits of the // image id (optional). - if (cpu_cell->cc_idx[2]) - cur_img_id_higher8bits = diacritic_to_rowcolumn(cpu_cell->cc_idx[2]); + if (self->lc->count > 3 && self->lc->chars[3]) + cur_img_id_higher8bits = diacritic_to_rowcolumn(self->lc->chars[3]); } // The current run is continued if the lower 24 bits of the image id and // the placement id are the same as in the previous cell and everything @@ -2697,7 +2701,7 @@ screen_render_line_graphics(Screen *self, Line *line, int32_t row) { prev_img_row - 1, run_length, 1, self->cell_size); } // Start a new run. - if (cpu_cell->ch == IMAGE_PLACEHOLDER_CHAR) { + if (cell_first_char(cpu_cell, self->text_cache) == IMAGE_PLACEHOLDER_CHAR) { run_length = 1; if (!cur_img_col) cur_img_col = 1; if (!cur_img_row) cur_img_row = 1; @@ -2755,7 +2759,7 @@ screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_dat for (index_type y = 0; y < self->lines; y++) { linebuf_init_line(linebuf, y); if (linebuf->line->attrs.has_dirty_text) { - render_line(fonts_data, linebuf->line, y, &self->paused_rendering.cursor, self->disable_ligatures); + render_line(fonts_data, linebuf->line, y, &self->paused_rendering.cursor, self->disable_ligatures, self->lc); screen_render_line_graphics(self, linebuf->line, y); if (linebuf->line->attrs.has_dirty_text && screen_has_marker(self)) mark_text_in_line(self->marker, linebuf->line); linebuf_mark_line_clean(linebuf, y); @@ -2780,7 +2784,7 @@ screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_dat // the unicode placeholder was first scanned can alter it. screen_render_line_graphics(self, self->historybuf->line, y - self->scrolled_by); if (self->historybuf->line->attrs.has_dirty_text) { - render_line(fonts_data, self->historybuf->line, lnum, self->cursor, self->disable_ligatures); + render_line(fonts_data, self->historybuf->line, lnum, self->cursor, self->disable_ligatures, self->lc); if (screen_has_marker(self)) mark_text_in_line(self->marker, self->historybuf->line); historybuf_mark_line_clean(self->historybuf, lnum); } @@ -2791,7 +2795,7 @@ screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_dat linebuf_init_line(self->linebuf, lnum); if (self->linebuf->line->attrs.has_dirty_text || (cursor_has_moved && (self->cursor->y == lnum || self->last_rendered.cursor_y == lnum))) { - render_line(fonts_data, self->linebuf->line, lnum, self->cursor, self->disable_ligatures); + render_line(fonts_data, self->linebuf->line, lnum, self->cursor, self->disable_ligatures, self->lc); screen_render_line_graphics(self, self->linebuf->line, y - self->scrolled_by); if (self->linebuf->line->attrs.has_dirty_text && screen_has_marker(self)) mark_text_in_line(self->marker, self->linebuf->line); if (is_overlay_active && lnum == self->overlay_line.ynum) render_overlay_line(self, self->linebuf->line, fonts_data); @@ -3032,9 +3036,9 @@ limit_without_trailing_whitespace(const Line *line, index_type limit) { if (!limit) return limit; if (limit > line->xnum) limit = line->xnum; while (limit > 0) { - CPUCell *cell = line->cpu_cells + limit - 1; - if (cell->cc_idx[0]) break; - switch(cell->ch) { + const CPUCell *cell = line->cpu_cells + limit - 1; + if (cell->ch_is_idx) break; + switch(cell->ch_or_idx) { case ' ': case '\t': case '\n': case '\r': case 0: break; default: return limit; @@ -3216,13 +3220,13 @@ extend_url(Screen *screen, Line *line, index_type *x, index_type *y, char_type s next_line_starts_with_url_chars = line_startswith_url_chars(line, in_hostname); has_newline = !line->attrs.is_continued; if (next_line_starts_with_url_chars && has_newline && !newlines_allowed) next_line_starts_with_url_chars = false; - if (sentinel && next_line_starts_with_url_chars && line->cpu_cells[0].ch == sentinel) next_line_starts_with_url_chars = false; + if (sentinel && next_line_starts_with_url_chars && cell_is_char(line->cpu_cells, sentinel)) next_line_starts_with_url_chars = false; } line = screen_visual_line(screen, *y + 1); if (!line) break; if (in_hostname) { for (last_hostname_char_pos = 0; last_hostname_char_pos < line->xnum; last_hostname_char_pos++) { - if (line->cpu_cells[last_hostname_char_pos].ch == '/') { + if (cell_is_char(line->cpu_cells + last_hostname_char_pos, '/')) { if (last_hostname_char_pos > 0) last_hostname_char_pos--; else { in_hostname = false; last_hostname_char_pos = line->xnum; } break; @@ -3235,7 +3239,7 @@ extend_url(Screen *screen, Line *line, index_type *x, index_type *y, char_type s } if (sentinel && *x == 0 && *y > orig_y) { line = screen_visual_line(screen, *y); - if (line && line->cpu_cells[0].ch == sentinel) { + if (line && cell_is_char(line->cpu_cells, sentinel)) { *y -= 1; *x = line->xnum - 1; } } @@ -3244,7 +3248,7 @@ extend_url(Screen *screen, Line *line, index_type *x, index_type *y, char_type s static char_type get_url_sentinel(Line *line, index_type url_start) { char_type before = 0, sentinel; - if (url_start > 0 && url_start < line->xnum) before = line->cpu_cells[url_start - 1].ch; + if (url_start > 0 && url_start < line->xnum) before = cell_first_char(line->cpu_cells + url_start - 1, line->text_cache); switch(before) { case '"': case '\'': @@ -3291,8 +3295,7 @@ screen_detect_url(Screen *screen, unsigned int x, unsigned int y) { sentinel = get_url_sentinel(line, url_start); index_type slash_count = 0; for (index_type i = url_start; i < line->xnum; i++) { - const char_type ch = line->cpu_cells[i].ch; - if (ch == '/' && ++slash_count > 2) { last_hostname_char_pos = i - 1; break; } + if (cell_is_char(line->cpu_cells + i, '/') && ++slash_count > 2) { last_hostname_char_pos = i - 1; break; } } url_end = line_url_end_at(line, x, true, sentinel, next_line_starts_with_url_chars, x <= last_hostname_char_pos, last_hostname_char_pos); } @@ -3442,7 +3445,7 @@ render_overlay_line(Screen *self, Line *line, FONTS_DATA_HANDLE fonts_data) { #define ol self->overlay_line line_save_cells(line, 0, line->xnum, ol.original_line.gpu_cells, ol.original_line.cpu_cells); screen_draw_overlay_line(self); - render_line(fonts_data, line, ol.ynum, self->cursor, self->disable_ligatures); + render_line(fonts_data, line, ol.ynum, self->cursor, self->disable_ligatures, self->lc); line_save_cells(line, 0, line->xnum, ol.gpu_cells, ol.cpu_cells); line_reset_cells(line, 0, line->xnum, ol.original_line.gpu_cells, ol.original_line.cpu_cells); ol.is_dirty = false; @@ -4028,14 +4031,18 @@ text_for_marked_url(Screen *self, PyObject *args) { return text_for_selections(self, &self->url_ranges, ansi, strip_trailing_whitespace); } +static bool +cell_is_blank(const CPUCell *c) { + return !cell_has_text(c) || cell_is_char(c, ' '); +} bool screen_selection_range_for_line(Screen *self, index_type y, index_type *start, index_type *end) { if (y >= self->lines) { return false; } Line *line = visual_line_(self, y); index_type xlimit = line->xnum, xstart = 0; - while (xlimit > 0 && CHAR_IS_BLANK(line->cpu_cells[xlimit - 1].ch)) xlimit--; - while (xstart < xlimit && CHAR_IS_BLANK(line->cpu_cells[xstart].ch)) xstart++; + while (xlimit > 0 && cell_is_blank(line->cpu_cells + xlimit - 1)) xlimit--; + while (xstart < xlimit && cell_is_blank(line->cpu_cells + xstart)) xstart++; *start = xstart; *end = xlimit > 0 ? xlimit - 1 : 0; return true; } @@ -4060,11 +4067,10 @@ is_opt_word_char(char_type ch, bool forward) { static bool is_char_ok_for_word_extension(Line* line, index_type x, bool forward) { - char_type ch = line->cpu_cells[x].ch; + char_type ch = cell_first_char(line->cpu_cells + x, line->text_cache); if (is_word_char(ch) || is_opt_word_char(ch, forward)) return true; // pass : from :// so that common URLs are matched - if (ch == ':' && x + 2 < line->xnum && line->cpu_cells[x+1].ch == '/' && line->cpu_cells[x+2].ch == '/') return true; - return false; + return ch == ':' && x + 2 < line->xnum && cell_is_char(line->cpu_cells + x + 1, '/') && cell_is_char(line->cpu_cells + x + 2, '/'); } bool diff --git a/kitty/screen.h b/kitty/screen.h index 6d8adc4ea..f9032c2b2 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -172,6 +172,7 @@ typedef struct { Selections selections, url_ranges; } paused_rendering; CharsetState charset; + ListOfChars *lc; } Screen; diff --git a/kitty/text-cache.c b/kitty/text-cache.c index d5ad0c5c0..7e34ea656 100644 --- a/kitty/text-cache.c +++ b/kitty/text-cache.c @@ -13,7 +13,7 @@ typedef struct Chars { #define NAME chars_map #define KEY_TY Chars -#define VAL_TY CharOrIndex +#define VAL_TY char_type static uint64_t hash_chars(Chars k); static bool cmpr_chars(Chars a, Chars b); #define HASH_FN hash_chars @@ -21,7 +21,7 @@ static bool cmpr_chars(Chars a, Chars b); #include "kitty-verstable.h" typedef struct TextCache { - struct { Chars *items; size_t count, capacity; } array; + struct { Chars *items; size_t capacity; char_type count; } array; chars_map map; unsigned refcnt; } TextCache; @@ -52,7 +52,7 @@ tc_clear(TextCache *ans) { static void free_text_cache(TextCache *self) { vt_cleanup(&self->map); - for (size_t i = 0; i < self->array.count; i++) free((char_type*)self->array.items[i].chars); + for (char_type i = 0; i < self->array.count; i++) free((char_type*)self->array.items[i].chars); free(self->array.items); free(self); } @@ -69,32 +69,49 @@ tc_decref(TextCache *self) { return NULL; } +char_type +tc_first_char_at_index(const TextCache *self, char_type idx) { + if (self->array.count > idx) return self->array.items[idx].chars[0]; + return 0; +} + + void -tc_chars_at_index(const TextCache *self, CharOrIndex idx, ListOfChars *ans) { - if (idx.ch_is_index) { - if (self->array.count > idx.ch) { - ans->count = self->array.items[idx.ch].count; - ensure_space_for(ans, chars, char_type, ans->count, capacity, 8, false); - memcpy(ans->chars, self->array.items[idx.ch].chars, sizeof(ans->chars[0]) * ans->count); - } else { - ans->count = 0; - } +tc_chars_at_index(const TextCache *self, char_type idx, ListOfChars *ans) { + if (self->array.count > idx) { + ans->count = self->array.items[idx].count; + ensure_space_for_chars(ans, ans->count); + memcpy(ans->chars, self->array.items[idx].chars, sizeof(ans->chars[0]) * ans->count); } else { - ans->count = 1; - ensure_space_for(ans, chars, char_type, 1, capacity, 8, false); - ans->chars[0] = idx.ch; + ans->count = 0; } } -static CharOrIndex +unsigned +tc_num_codepoints(const TextCache *self, char_type idx) { + return self->array.count > idx ? self->array.items[idx].count : 0; +} + +unsigned +tc_chars_at_index_ansi(const TextCache *self, char_type idx, ANSIBuf *output) { + unsigned count = 0; + if (self->array.count > idx) { + count = self->array.items[idx].count; + ensure_space_for(output, buf, output->buf[0], output->len + count, capacity, 2048, false); + memcpy(output->buf + output->len, self->array.items[idx].chars, sizeof(output->buf[0]) * count); + output->len += count; + } + return count; +} + +static char_type copy_and_insert(TextCache *self, const Chars key) { - if (self->array.count >= (1llu << (8*sizeof(char_type) - 1)) - 1) fatal("Too many items in TextCache"); + if (self->array.count > MAX_CHAR_TYPE_VALUE) fatal("Too many items in TextCache"); ensure_space_for(&(self->array), items, Chars, self->array.count + 1, capacity, 256, false); char_type *copy = malloc(key.count * sizeof(key.chars[0])); if (!copy) fatal("Out of memory"); memcpy(copy, key.chars, key.count * sizeof(key.chars[0])); - CharOrIndex ans; - ans.ch_is_index = 1; ans.ch = self->array.count; + char_type ans = self->array.count; Chars *k = self->array.items + self->array.count++; k->count = key.count; k->chars = copy; chars_map_itr i = vt_insert(&self->map, *k, ans); @@ -102,13 +119,8 @@ copy_and_insert(TextCache *self, const Chars key) { return ans; } -CharOrIndex +char_type tc_get_or_insert_chars(TextCache *self, const ListOfChars *chars) { - if (chars->count == 1) { - CharOrIndex ans = {0}; - ans.ch = chars->chars[0]; - return ans; - } Chars key = {.count=chars->count, .chars=chars->chars}; chars_map_itr i = vt_get(&self->map, key); if (vt_is_end(i)) return copy_and_insert(self, key); diff --git a/kitty/text-cache.h b/kitty/text-cache.h index ef7ccbca2..ae609410c 100644 --- a/kitty/text-cache.h +++ b/kitty/text-cache.h @@ -14,6 +14,33 @@ typedef struct ListOfChars { size_t count, capacity; } ListOfChars; +#define LIST_OF_CHARS_STACK_SIZE 4 +static inline void cleanup_list_of_chars(ListOfChars *lc) { if (lc->capacity > LIST_OF_CHARS_STACK_SIZE) free(lc->chars); } +#define RAII_ListOfChars(name) char_type name##lcbuf[LIST_OF_CHARS_STACK_SIZE]; __attribute__((cleanup(cleanup_list_of_chars))) ListOfChars name = {.chars=name##lcbuf, .capacity = LIST_OF_CHARS_STACK_SIZE}; +static inline ListOfChars* alloc_list_of_chars(void) { + ListOfChars *ans = calloc(1, sizeof(ListOfChars)); + if (ans) { + ans->capacity = LIST_OF_CHARS_STACK_SIZE * 2; + ans->chars = malloc(ans->capacity * sizeof(ans->chars[0])); + if (!ans->chars) { free(ans); ans = NULL; } + } + return ans; +} + +static inline void +ensure_space_for_chars(ListOfChars *lc, size_t count) { + if (lc->capacity >= count) return; + if (lc->capacity > LIST_OF_CHARS_STACK_SIZE) { + ensure_space_for(lc, chars, char_type, count, capacity, count, false); + } else { + lc->capacity = count + LIST_OF_CHARS_STACK_SIZE; + void *chars = malloc(lc->capacity * sizeof(lc->chars[0])); + if (!chars) fatal("Out of memory allocating LCChars char space"); + memcpy(chars, lc->chars, lc->count * sizeof(lc->chars[0])); + lc->chars = chars; + } +} + #ifndef TEXT_CACHE_IMPLEMENTATION typedef struct {int x; } *TextCache; #endif @@ -22,5 +49,8 @@ TextCache* tc_alloc(void); TextCache* tc_incref(TextCache *self); TextCache* tc_decref(TextCache *self); void tc_clear(TextCache *ans); -void tc_chars_at_index(const TextCache *self, CharOrIndex idx, ListOfChars *ans); -CharOrIndex tc_get_or_insert_chars(TextCache *self, const ListOfChars *chars); +void tc_chars_at_index(const TextCache *self, char_type idx, ListOfChars *ans); +unsigned tc_chars_at_index_ansi(const TextCache *self, char_type idx, ANSIBuf *output); +char_type tc_get_or_insert_chars(TextCache *self, const ListOfChars *chars); +char_type tc_first_char_at_index(const TextCache *self, char_type idx); +unsigned tc_num_codepoints(const TextCache *self, char_type idx); diff --git a/kitty/unicode-data.h b/kitty/unicode-data.h index 9f625d6ba..457a0afc9 100644 --- a/kitty/unicode-data.h +++ b/kitty/unicode-data.h @@ -1,9 +1,6 @@ #pragma once #include "data-types.h" #include "state.h" -// START_KNOWN_MARKS -static const combining_type VS15 = 1364, VS16 = 1365; -// END_KNOWN_MARKS // Converts row/column diacritics to numbers. int diacritic_to_num(char_type ch); @@ -14,8 +11,6 @@ bool is_word_char(char_type ch); bool is_CZ_category(char_type); bool is_P_category(char_type); bool is_non_rendered_char(char_type); -char_type codepoint_for_mark(combining_type m); -combining_type mark_for_codepoint(char_type c); static inline bool is_excluded_from_url(uint32_t ch) {