diff --git a/kitty/cursor.c b/kitty/cursor.c index fbf0fc51c..4378f2a27 100644 --- a/kitty/cursor.c +++ b/kitty/cursor.c @@ -138,12 +138,9 @@ END_ALLOW_CASE_RANGE void apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, int *params, unsigned int count) { #define RANGE for(unsigned c = 0; c < cell_count; c++, cell++) -#define SET(shift) RANGE { cell->attrs |= (1 << shift); } break; -#define RESET(shift) RANGE { cell->attrs &= ~(1 << shift); } break; -#define RESET2(shift1, shift2) RANGE { cell->attrs &= ~((1 << shift1) | (1 << shift2)); } break; -#define SETM(val, mask, shift) { RANGE { cell->attrs &= ~(mask << shift); cell->attrs |= ((val) << shift); } break; } #define SET_COLOR(which) { color_type color = 0; parse_color(params, &i, count, &color); if (color) { RANGE { cell->which = color; }} } break; #define SIMPLE(which, val) RANGE { cell->which = (val); } break; +#define S(which, val) RANGE { cell->attrs.bits.which = (val); } break; unsigned int i = 0, attr; if (!count) { params[0] = 0; count = 1; } @@ -151,34 +148,38 @@ apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, int *params, un GPUCell *cell = first_cell; attr = params[i++]; switch(attr) { - case 0: - RANGE { cell->attrs &= WIDTH_MASK; cell->fg = 0; cell->bg = 0; cell->decoration_fg = 0; } + case 0: { + const CellAttrs remove_sgr_mask = {.val=~SGR_MASK}; + RANGE { cell->attrs.val &= remove_sgr_mask.val; cell->fg = 0; cell->bg = 0; cell->decoration_fg = 0; } + } break; case 1: - SET(BOLD_SHIFT); + S(bold, true); case 2: - SET(DIM_SHIFT); + S(dim, true); case 3: - SET(ITALIC_SHIFT); - case 4: - if (i < count) { uint8_t val = MIN(3, params[i]); i++; SETM(val, DECORATION_MASK, DECORATION_SHIFT); } - else { SETM(1, DECORATION_MASK, DECORATION_SHIFT); } + S(italic, true); + case 4: { + uint8_t val = 1; + if (i < count) { val = MIN(3, params[i]); i++; } + S(decoration, val); + } case 7: - SET(REVERSE_SHIFT); + S(reverse, true); case 9: - SET(STRIKE_SHIFT); + S(strike, true); case 21: - SETM(2, DECORATION_MASK, DECORATION_SHIFT); + S(decoration, 2); case 22: - RESET2(DIM_SHIFT, BOLD_SHIFT); + RANGE { cell->attrs.bits.bold = false; cell->attrs.bits.dim = false; } break; case 23: - RESET(ITALIC_SHIFT); + S(italic, false); case 24: - SETM(0, DECORATION_MASK, DECORATION_SHIFT); + S(decoration, 0); case 27: - RESET(REVERSE_SHIFT); + S(reverse, false); case 29: - RESET(STRIKE_SHIFT); + S(strike, false); START_ALLOW_CASE_RANGE case 30 ... 37: SIMPLE(fg, ((attr - 30) << 8) | 1); @@ -203,18 +204,16 @@ END_ALLOW_CASE_RANGE SIMPLE(decoration_fg, 0); } } -#undef RESET -#undef RESET2 #undef SET_COLOR -#undef SET -#undef SETM #undef RANGE +#undef SIMPLE +#undef S } const char* cursor_as_sgr(const Cursor *self) { GPUCell blank_cell = { 0 }, cursor_cell = { - .attrs = CURSOR_TO_ATTRS(self, 1), + .attrs = cursor_to_attrs(self, 1), .fg = self->fg & COL_MASK, .bg = self->bg & COL_MASK, .decoration_fg = self->decoration_fg & COL_MASK, diff --git a/kitty/data-types.c b/kitty/data-types.c index 78a13506c..a7d703c8f 100644 --- a/kitty/data-types.c +++ b/kitty/data-types.c @@ -235,10 +235,26 @@ extern bool init_freetype_library(PyObject*); extern bool init_freetype_render_ui_text(PyObject*); #endif +static unsigned +shift_to_first_set_bit(CellAttrs x) { + unsigned num_of_bits = 8 * sizeof(x.val); + unsigned ans = 0; + while (num_of_bits--) { + if (x.val & 1) return ans; + x.val >>= 1; + ans++; + } + return ans; +} + EXPORTED PyMODINIT_FUNC PyInit_fast_data_types(void) { PyObject *m; + if (sizeof(CellAttrs) != 2u) { + PyErr_SetString(PyExc_RuntimeError, "Size of CellAttrs is not 2 on this platform"); + return NULL; + } m = PyModule_Create(&module); if (m == NULL) return NULL; @@ -278,13 +294,11 @@ PyInit_fast_data_types(void) { #endif if (!init_fonts(m)) return NULL; - PyModule_AddIntConstant(m, "BOLD", BOLD_SHIFT); - PyModule_AddIntConstant(m, "ITALIC", ITALIC_SHIFT); - PyModule_AddIntConstant(m, "REVERSE", REVERSE_SHIFT); - PyModule_AddIntConstant(m, "STRIKETHROUGH", STRIKE_SHIFT); - PyModule_AddIntConstant(m, "DIM", DIM_SHIFT); - PyModule_AddIntConstant(m, "DECORATION", DECORATION_SHIFT); - PyModule_AddIntConstant(m, "MARK", MARK_SHIFT); + CellAttrs a; +#define s(name, attr) { a.val = 0; a.bits.attr = 1; PyModule_AddIntConstant(m, #name, shift_to_first_set_bit(a)); } + s(BOLD, bold); s(ITALIC, italic); s(REVERSE, reverse); s(MARK, mark); + s(STRIKETHROUGH, strike); s(DIM, dim); s(DECORATION, decoration); +#undef s PyModule_AddIntConstant(m, "MARK_MASK", MARK_MASK); PyModule_AddStringMacro(m, ERROR_PREFIX); #ifdef KITTY_VCS_REV diff --git a/kitty/data-types.h b/kitty/data-types.h index 75345b6a5..e4ff1e5a9 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -52,7 +52,6 @@ typedef uint16_t glyph_index; typedef uint32_t pixel; typedef unsigned int index_type; typedef uint16_t sprite_index; -typedef uint16_t attrs_type; typedef enum CursorShapes { NO_CURSOR_SHAPE, CURSOR_BLOCK, CURSOR_BEAM, CURSOR_UNDERLINE, NUM_OF_CURSOR_SHAPES } CursorShape; typedef enum { DISABLE_LIGATURES_NEVER, DISABLE_LIGATURES_CURSOR, DISABLE_LIGATURES_ALWAYS } DisableLigature; @@ -65,20 +64,6 @@ typedef enum { TILING, SCALED, MIRRORED } BackgroundImageLayout; #define MAX_CHILDREN 512 #define BLANK_CHAR 0 -#define ATTRS_MASK_WITHOUT_WIDTH 0xFFFC -#define WIDTH_MASK 3 -#define DECORATION_SHIFT 2 -#define DECORATION_MASK 3 -#define BOLD_SHIFT 4 -#define ITALIC_SHIFT 5 -#define BI_VAL(attrs) ((attrs >> 4) & 3) -#define REVERSE_SHIFT 6 -#define STRIKE_SHIFT 7 -#define DIM_SHIFT 8 -#define MARK_SHIFT 9 -#define ATTRS_MASK_WITHOUT_MARK 0xf9ff -#define ATTRS_MASK_FOR_SGR (ATTRS_MASK_WITHOUT_MARK | ATTRS_MASK_WITHOUT_WIDTH) -#define MARK_MASK 3 #define COL_MASK 0xFFFFFFFF #define DECORATION_FG_CODE 58 #define CHAR_IS_BLANK(ch) ((ch) == 32 || (ch) == 0) @@ -86,15 +71,6 @@ typedef enum { TILING, SCALED, MIRRORED } BackgroundImageLayout; #define FG 1 #define BG 2 -#define CURSOR_TO_ATTRS(c, w) \ - ((w) | (((c->decoration & 3) << DECORATION_SHIFT) | ((c->bold & 1) << BOLD_SHIFT) | \ - ((c->italic & 1) << ITALIC_SHIFT) | ((c->reverse & 1) << REVERSE_SHIFT) | \ - ((c->strikethrough & 1) << STRIKE_SHIFT) | ((c->dim & 1) << DIM_SHIFT))) - -#define ATTRS_TO_CURSOR(a, c) \ - (c)->decoration = (a >> DECORATION_SHIFT) & 3; (c)->bold = (a >> BOLD_SHIFT) & 1; (c)->italic = (a >> ITALIC_SHIFT) & 1; \ - (c)->reverse = (a >> REVERSE_SHIFT) & 1; (c)->strikethrough = (a >> STRIKE_SHIFT) & 1; (c)->dim = (a >> DIM_SHIFT) & 1; - #define COPY_CELL(src, s, dest, d) \ (dest)->cpu_cells[d] = (src)->cpu_cells[s]; (dest)->gpu_cells[d] = (src)->gpu_cells[s]; @@ -155,10 +131,27 @@ typedef struct { uint32_t left, top, right, bottom; } Region; +typedef union CellAttrs { + struct { + uint16_t width : 2; + uint16_t decoration : 2; + uint16_t bold : 1; + uint16_t italic : 1; + uint16_t reverse : 1; + uint16_t strike : 1; + uint16_t dim : 1; + uint16_t mark : 2; + } bits; + uint16_t val; +} CellAttrs; +#define MARK_MASK (3u) +#define WIDTH_MASK (3u) +#define SGR_MASK (~(((CellAttrs){.bits={.width=WIDTH_MASK, .mark=MARK_MASK}}).val)) + typedef struct { color_type fg, bg, decoration_fg; sprite_index sprite_x, sprite_y, sprite_z; - attrs_type attrs; + CellAttrs attrs; } GPUCell; typedef struct { @@ -295,6 +288,21 @@ typedef struct {FONTS_DATA_HEAD} *FONTS_DATA_HANDLE; memmove((array) + (i), (array) + (i) + 1, sizeof((array)[0]) * ((count) - (i))); \ }} +static inline CellAttrs +cursor_to_attrs(const Cursor *c, const uint16_t width) { + CellAttrs ans = {.bits={ + .width=width, .decoration=c->decoration, .bold=c->bold, .italic=c->italic, .reverse=c->reverse, + .strike=c->strikethrough, .dim=c->dim}}; + return ans; +} + +static inline void +attrs_to_cursor(const CellAttrs attrs, Cursor *c) { + c->decoration = attrs.bits.decoration; c->bold = attrs.bits.bold; c->italic = attrs.bits.italic; + c->reverse = attrs.bits.reverse; c->strikethrough = attrs.bits.strike; c->dim = attrs.bits.dim; +} + + // Global functions const char* base64_decode(const uint32_t *src, size_t src_sz, uint8_t *dest, size_t dest_capacity, size_t *dest_sz); Line* alloc_line(void); diff --git a/kitty/fonts.c b/kitty/fonts.c index 250bfd38d..13b39f21e 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -378,7 +378,7 @@ face_has_codepoint(PyObject* face, char_type cp) { static bool has_emoji_presentation(CPUCell *cpu_cell, GPUCell *gpu_cell) { - return (gpu_cell->attrs & WIDTH_MASK) == 2 && is_emoji(cpu_cell->ch) && cpu_cell->cc_idx[0] != VS15; + return gpu_cell->attrs.bits.width == 2 && is_emoji(cpu_cell->ch) && cpu_cell->cc_idx[0] != VS15; } static bool @@ -458,8 +458,8 @@ load_fallback_font(FontGroup *fg, CPUCell *cell, bool bold, bool italic, bool em static ssize_t fallback_font(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell) { - bool bold = (gpu_cell->attrs >> BOLD_SHIFT) & 1; - bool italic = (gpu_cell->attrs >> ITALIC_SHIFT) & 1; + bool bold = gpu_cell->attrs.bits.bold; + bool italic = gpu_cell->attrs.bits.italic; bool emoji_presentation = has_emoji_presentation(cpu_cell, gpu_cell); // Check if one of the existing fallback fonts has this text @@ -512,7 +512,7 @@ START_ALLOW_CASE_RANGE default: ans = in_symbol_maps(fg, cpu_cell->ch); if (ans > -1) return ans; - switch(BI_VAL(gpu_cell->attrs)) { + switch(gpu_cell->attrs.bits.bold | (gpu_cell->attrs.bits.italic << 1)) { case 0: ans = fg->medium_font_idx; break; case 1: @@ -601,11 +601,11 @@ load_hb_buffer(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_ index_type num; hb_buffer_clear_contents(harfbuzz_buffer); while (num_cells) { - attrs_type prev_width = 0; + 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--) { if (prev_width == 2) { prev_width = 0; continue; } shape_buffer[num++] = first_cpu_cell->ch; - prev_width = first_gpu_cell->attrs & WIDTH_MASK; + prev_width = first_gpu_cell->attrs.bits.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]); } @@ -660,7 +660,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_MASK) == 2 && is_emoji(cpu_cells->ch); + bool was_colored = gpu_cells->attrs.bits.width == 2 && is_emoji(cpu_cells->ch); 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(); @@ -780,7 +780,7 @@ static unsigned int check_cell_consumed(CellData *cell_data, CPUCell *last_cpu_cell) { cell_data->codepoints_consumed++; if (cell_data->codepoints_consumed >= cell_data->num_codepoints) { - attrs_type width = cell_data->gpu_cell->attrs & WIDTH_MASK; + uint16_t width = cell_data->gpu_cell->attrs.bits.width; cell_data->cpu_cell += MAX(1, width); cell_data->gpu_cell += MAX(1, width); cell_data->codepoints_consumed = 0; @@ -829,7 +829,8 @@ ligature_type_from_glyph_name(const char *glyph_name, SpacerStrategy strategy) { static void detect_spacer_strategy(hb_font_t *hbf, Font *font) { CPUCell cpu_cells[3] = {{.ch = '='}, {.ch = '='}, {.ch = '='}}; - GPUCell gpu_cells[3] = {{.attrs = 1}, {.attrs = 1}, {.attrs = 1}}; + const CellAttrs w1 = {.bits={.width=1}}; + GPUCell gpu_cells[3] = {{.attrs = w1}, {.attrs = w1}, {.attrs = w1}}; shape(cpu_cells, gpu_cells, arraysz(cpu_cells), hbf, font, false); font->spacer_strategy = SPACERS_BEFORE; if (G(num_glyphs) > 1) { @@ -1087,7 +1088,7 @@ merge_groups_for_pua_space_ligature(void) { static bool is_group_calt_ligature(const Group *group) { GPUCell *first_cell = G(first_gpu_cell) + group->first_cell_idx; - return group->num_cells > 1 && group->has_special_glyph && (first_cell->attrs & WIDTH_MASK) == 1; + return group->num_cells > 1 && group->has_special_glyph && first_cell->attrs.bits.width == 1; } @@ -1141,7 +1142,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_MASK; + while(num < line->xnum && line->cpu_cells[num].ch) num += line->gpu_cells[num].attrs.bits.width; PyObject *face = NULL; Font *font; if (!num_font_groups) { PyErr_SetString(PyExc_RuntimeError, "must create at least one font group first"); return NULL; } @@ -1228,7 +1229,7 @@ render_line(FONTS_DATA_HANDLE fg_, Line *line, index_type lnum, Cursor *cursor, bool center_glyph = false; bool disable_ligature_at_cursor = cursor != NULL && disable_ligature_strategy == DISABLE_LIGATURES_CURSOR && lnum == cursor->y; index_type first_cell_in_run, i; - attrs_type prev_width = 0; + uint16_t prev_width = 0; for (i=0, first_cell_in_run=0; i < line->xnum; i++) { if (prev_width == 2) { prev_width = 0; continue; } CPUCell *cpu_cell = line->cpu_cells + i; @@ -1271,12 +1272,12 @@ render_line(FONTS_DATA_HANDLE fg_, Line *line, index_type lnum, Cursor *cursor, render_run(fg, line->cpu_cells + i, line->gpu_cells + i, num_spaces + 1, cell_font_idx, true, center_glyph, -1, disable_ligature_strategy); run_font_idx = NO_FONT; first_cell_in_run = i + num_spaces + 1; - prev_width = line->gpu_cells[i+num_spaces].attrs & WIDTH_MASK; + prev_width = line->gpu_cells[i+num_spaces].attrs.bits.width; i += num_spaces; continue; } } - prev_width = gpu_cell->attrs & WIDTH_MASK; + prev_width = gpu_cell->attrs.bits.width; if (run_font_idx == NO_FONT) run_font_idx = cell_font_idx; if (run_font_idx == cell_font_idx) continue; RENDER @@ -1548,8 +1549,8 @@ get_fallback_font(PyObject UNUSED *self, PyObject *args) { 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]); - if (bold) gpu_cell.attrs |= 1 << BOLD_SHIFT; - if (italic) gpu_cell.attrs |= 1 << ITALIC_SHIFT; + if (bold) gpu_cell.attrs.bits.bold = true; + if (italic) gpu_cell.attrs.bits.italic = true; FontGroup *fg = font_groups; ssize_t ans = fallback_font(fg, &cpu_cell, &gpu_cell); if (ans == MISSING_FONT) { PyErr_SetString(PyExc_ValueError, "No fallback font found"); return NULL; } diff --git a/kitty/line-buf.c b/kitty/line-buf.c index 392e290a0..7e02b1511 100644 --- a/kitty/line-buf.c +++ b/kitty/line-buf.c @@ -161,24 +161,21 @@ line(LineBuf *self, PyObject *y) { unsigned int linebuf_char_width_at(LineBuf *self, index_type x, index_type y) { - return (gpu_lineptr(self, self->line_map[y])[x].attrs) & WIDTH_MASK; -} - -void -linebuf_set_attribute(LineBuf *self, unsigned int shift, unsigned int val) { - for (index_type y = 0; y < self->ynum; y++) { - set_attribute_on_line(gpu_lineptr(self, y), shift, val, self->xnum); - self->line_attrs[y].bits.has_dirty_text = true; - } + return gpu_lineptr(self, self->line_map[y])[x].attrs.bits.width; } static PyObject* set_attribute(LineBuf *self, PyObject *args) { #define set_attribute_doc "set_attribute(which, val) -> Set the attribute on all cells in the line." - unsigned int shift, val; - if (!PyArg_ParseTuple(args, "II", &shift, &val)) return NULL; - if (shift < DECORATION_SHIFT || shift > DIM_SHIFT) { PyErr_SetString(PyExc_ValueError, "Unknown attribute"); return NULL; } - linebuf_set_attribute(self, shift, val); + unsigned int val; + char *which; + if (!PyArg_ParseTuple(args, "sI", &which, &val)) return NULL; + for (index_type y = 0; y < self->ynum; y++) { + if (!set_named_attribute_on_line(gpu_lineptr(self, y), which, val, self->xnum)) { + PyErr_SetString(PyExc_KeyError, "Unknown cell attribute"); return NULL; + } + self->line_attrs[y].bits.has_dirty_text = true; + } Py_RETURN_NONE; } diff --git a/kitty/line.c b/kitty/line.c index 31eb9b378..724d1d506 100644 --- a/kitty/line.c +++ b/kitty/line.c @@ -249,7 +249,7 @@ unicode_in_range(const Line *self, const index_type start, const index_type limi } else { n += cell_as_unicode(self->cpu_cells + i, include_cc, buf + n, ' '); } - previous_width = self->gpu_cells[i].attrs & WIDTH_MASK; + previous_width = self->gpu_cells[i].attrs.bits.width; } return PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, n); } @@ -315,6 +315,7 @@ line_as_ansi(Line *self, ANSIBuf *output, const GPUCell** prev_cell) { static const GPUCell blank_cell = { 0 }; GPUCell *cell; if (*prev_cell == NULL) *prev_cell = &blank_cell; + const CellAttrs mask_for_sgr = {.val=SGR_MASK}; for (index_type pos=0; pos < limit; pos++) { char_type ch = self->cpu_cells[pos].ch; @@ -331,7 +332,7 @@ line_as_ansi(Line *self, ANSIBuf *output, const GPUCell** prev_cell) { cell = &self->gpu_cells[pos]; -#define CMP_ATTRS (cell->attrs & ATTRS_MASK_FOR_SGR) != ((*prev_cell)->attrs & ATTRS_MASK_FOR_SGR) +#define CMP_ATTRS (cell->attrs.val & mask_for_sgr.val) != ((*prev_cell)->attrs.val & mask_for_sgr.val) #define CMP(x) cell->x != (*prev_cell)->x if (CMP_ATTRS || CMP(fg) || CMP(bg) || CMP(decoration_fg)) { const char *sgr = cell_as_sgr(cell, *prev_cell); @@ -349,7 +350,7 @@ line_as_ansi(Line *self, ANSIBuf *output, const GPUCell** prev_cell) { WRITE_CH(codepoint_for_mark(self->cpu_cells[pos].cc_idx[c])); } } - previous_width = cell->attrs & WIDTH_MASK; + previous_width = cell->attrs.bits.width; } #undef CMP_ATTRS #undef CMP @@ -398,15 +399,14 @@ width(Line *self, PyObject *val) { #define width_doc "width(x) -> the width of the character at x" unsigned long x = PyLong_AsUnsignedLong(val); if (x >= self->xnum) { PyErr_SetString(PyExc_ValueError, "Out of bounds"); return NULL; } - char_type attrs = self->gpu_cells[x].attrs; - return PyLong_FromUnsignedLong((unsigned long) (attrs & WIDTH_MASK)); + return PyLong_FromUnsignedLong((unsigned long) (self->gpu_cells[x].attrs.bits.width)); } void line_add_combining_char(Line *self, uint32_t ch, unsigned int x) { CPUCell *cell = self->cpu_cells + x; if (!cell->ch) { - if (x > 0 && (self->gpu_cells[x-1].attrs & WIDTH_MASK) == 2 && self->cpu_cells[x-1].ch) cell = self->cpu_cells + x - 1; + if (x > 0 && (self->gpu_cells[x-1].attrs.bits.width) == 2 && self->cpu_cells[x-1].ch) cell = self->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++) { @@ -435,7 +435,6 @@ set_text(Line* self, PyObject *args) { #define set_text_doc "set_text(src, offset, sz, cursor) -> Set the characters and attributes from the specified text and cursor" PyObject *src; Py_ssize_t offset, sz, limit; - char_type attrs; Cursor *cursor; int kind; void *buf; @@ -452,7 +451,7 @@ set_text(Line* self, PyObject *args) { PyErr_SetString(PyExc_ValueError, "Out of bounds offset/sz"); return NULL; } - attrs = CURSOR_TO_ATTRS(cursor, 1); + CellAttrs attrs = cursor_to_attrs(cursor, 1); color_type fg = (cursor->fg & COL_MASK), bg = cursor->bg & COL_MASK; color_type dfg = cursor->decoration_fg & COL_MASK; @@ -482,8 +481,7 @@ cursor_from(Line* self, PyObject *args) { ans = alloc_cursor(); if (ans == NULL) { PyErr_NoMemory(); return NULL; } ans->x = x; ans->y = y; - char_type attrs = self->gpu_cells[x].attrs; - ATTRS_TO_CURSOR(attrs, ans); + attrs_to_cursor(self->gpu_cells[x].attrs, ans); ans->fg = self->gpu_cells[x].fg; ans->bg = self->gpu_cells[x].bg; ans->decoration_fg = self->gpu_cells[x].decoration_fg & COL_MASK; @@ -492,11 +490,11 @@ cursor_from(Line* self, PyObject *args) { void line_clear_text(Line *self, unsigned int at, unsigned int num, char_type ch) { - attrs_type width = ch ? 1 : 0; + const uint16_t width = ch ? 1 : 0; for (index_type i = at; i < MIN(self->xnum, at + num); i++) { self->cpu_cells[i].ch = ch; memset(self->cpu_cells[i].cc_idx, 0, sizeof(self->cpu_cells[i].cc_idx)); self->cpu_cells[i].hyperlink_id = 0; - self->gpu_cells[i].attrs = (self->gpu_cells[i].attrs & ATTRS_MASK_WITHOUT_WIDTH) | width; + self->gpu_cells[i].attrs.bits.width = width; } } @@ -512,10 +510,9 @@ clear_text(Line* self, PyObject *args) { void line_apply_cursor(Line *self, Cursor *cursor, unsigned int at, unsigned int num, bool clear_char) { - char_type attrs = CURSOR_TO_ATTRS(cursor, 1); + CellAttrs attrs = cursor_to_attrs(cursor, 0); color_type fg = (cursor->fg & COL_MASK), bg = (cursor->bg & COL_MASK); color_type dfg = cursor->decoration_fg & COL_MASK; - if (!clear_char) attrs = attrs & ATTRS_MASK_WITHOUT_WIDTH; for (index_type i = at; i < self->xnum && i < at + num; i++) { if (clear_char) { @@ -525,8 +522,9 @@ line_apply_cursor(Line *self, Cursor *cursor, unsigned int at, unsigned int num, self->gpu_cells[i].attrs = attrs; clear_sprite_position(self->gpu_cells[i]); } else { - attrs_type w = self->gpu_cells[i].attrs & WIDTH_MASK; - self->gpu_cells[i].attrs = attrs | w; + attrs.bits.width = self->gpu_cells[i].attrs.bits.width; + attrs.bits.mark = self->gpu_cells[i].attrs.bits.mark; + 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; @@ -549,11 +547,10 @@ void line_right_shift(Line *self, unsigned int at, unsigned int num) { COPY_SELF_CELL(i - num, i) } // Check if a wide character was split at the right edge - char_type w = (self->gpu_cells[self->xnum - 1].attrs) & WIDTH_MASK; - if (w != 1) { + if (self->gpu_cells[self->xnum - 1].attrs.bits.width != 1) { self->cpu_cells[self->xnum - 1].ch = BLANK_CHAR; self->cpu_cells[self->xnum - 1].hyperlink_id = 0; - self->gpu_cells[self->xnum - 1].attrs = BLANK_CHAR ? 1 : 0; + self->gpu_cells[self->xnum - 1].attrs = (CellAttrs){.bits={.width=BLANK_CHAR ? 1 : 0}}; clear_sprite_position(self->gpu_cells[self->xnum - 1]); } } @@ -589,7 +586,7 @@ left_shift(Line *self, PyObject *args) { 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_MASK) > 1) ch = self->cpu_cells[at-1].ch; + if (!ch && at > 0 && (self->gpu_cells[at-1].attrs.bits.width) > 1) ch = self->cpu_cells[at-1].ch; return ch; } @@ -597,9 +594,9 @@ 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; if (cursor == NULL) { - g->attrs = (self->gpu_cells[at].attrs & ATTRS_MASK_WITHOUT_WIDTH) | width; + g->attrs.bits.width = width; } else { - g->attrs = CURSOR_TO_ATTRS(cursor, width & WIDTH_MASK); + g->attrs = cursor_to_attrs(cursor, width); g->fg = cursor->fg & COL_MASK; g->bg = cursor->bg & COL_MASK; g->decoration_fg = cursor->decoration_fg & COL_MASK; @@ -629,10 +626,12 @@ set_char(Line *self, PyObject *args) { static PyObject* set_attribute(Line *self, PyObject *args) { #define set_attribute_doc "set_attribute(which, val) -> Set the attribute on all cells in the line." - unsigned int shift, val; - if (!PyArg_ParseTuple(args, "II", &shift, &val)) return NULL; - if (shift < DECORATION_SHIFT || shift > DIM_SHIFT) { PyErr_SetString(PyExc_ValueError, "Unknown attribute"); return NULL; } - set_attribute_on_line(self->gpu_cells, shift, val, self->xnum); + unsigned int val; + char *which; + if (!PyArg_ParseTuple(args, "sI", &which, &val)) return NULL; + if (!set_named_attribute_on_line(self->gpu_cells, which, val, self->xnum)) { + PyErr_SetString(PyExc_KeyError, "Unknown cell attribute"); return NULL; + } Py_RETURN_NONE; } @@ -669,32 +668,22 @@ cell_as_sgr(const GPUCell *cell, const GPUCell *prev) { #define SZ sizeof(buf) - (p - buf) - 2 #define P(s) { size_t len = strlen(s); if (SZ > len) { memcpy(p, s, len); p += len; } } char *p = buf; -#define CMP(attr) (attr(cell) != attr(prev)) -#define BOLD(cell) (cell->attrs & (1 << BOLD_SHIFT)) -#define DIM(cell) (cell->attrs & (1 << DIM_SHIFT)) -#define ITALIC(cell) (cell->attrs & (1 << ITALIC_SHIFT)) -#define REVERSE(cell) (cell->attrs & (1 << REVERSE_SHIFT)) -#define STRIKETHROUGH(cell) (cell->attrs & (1 << STRIKE_SHIFT)) -#define DECORATION(cell) (cell->attrs & (DECORATION_MASK << DECORATION_SHIFT)) - bool intensity_differs = CMP(BOLD) || CMP(DIM); +#define CA cell->attrs.bits +#define PA prev->attrs.bits + bool intensity_differs = CA.bold != PA.bold || CA.dim != PA.dim; if (intensity_differs) { - if (!BOLD(cell) && !DIM(cell)) { P("22;"); } - else { if (BOLD(cell)) P("1;"); if (DIM(cell)) P("2;"); } + if (!CA.bold && !CA.dim) { P("22;"); } + else { if (CA.bold) P("1;"); if (CA.dim) P("2;"); } } - if (CMP(ITALIC)) P(ITALIC(cell) ? "3;" : "23;"); - if (CMP(REVERSE)) P(REVERSE(cell) ? "7;" : "27;"); - if (CMP(STRIKETHROUGH)) P(STRIKETHROUGH(cell) ? "9;" : "29;"); + if (CA.italic != PA.italic) P(CA.italic ? "3;" : "23;"); + if (CA.reverse != PA.reverse) P(CA.reverse ? "7;" : "27;"); + if (CA.strike != PA.strike) P(CA.strike ? "9;" : "29;"); if (cell->fg != prev->fg) p += color_as_sgr(p, SZ, cell->fg, 30, 90, 38); if (cell->bg != prev->bg) p += color_as_sgr(p, SZ, cell->bg, 40, 100, 48); if (cell->decoration_fg != prev->decoration_fg) p += color_as_sgr(p, SZ, cell->decoration_fg, 0, 0, DECORATION_FG_CODE); - if (CMP(DECORATION)) P(decoration_as_sgr((cell->attrs >> DECORATION_SHIFT) & DECORATION_MASK)); -#undef CMP -#undef BOLD -#undef DIM -#undef ITALIC -#undef REVERSE -#undef STRIKETHROUGH -#undef DECORATION + if (CA.decoration != PA.decoration) P(decoration_as_sgr(CA.decoration)); +#undef PA +#undef CA #undef P #undef SZ if (p > buf) *(p - 1) = 0; // remove trailing semi-colon @@ -714,9 +703,9 @@ __eq__(Line *a, Line *b) { } bool -line_has_mark(Line *line, attrs_type mark) { +line_has_mark(Line *line, uint16_t mark) { for (index_type x = 0; x < line->xnum; x++) { - attrs_type m = (line->gpu_cells[x].attrs >> MARK_SHIFT) & MARK_MASK; + const uint16_t m = line->gpu_cells[x].attrs.bits.mark; if (m && (!mark || mark == m)) return true; } return false; @@ -731,8 +720,8 @@ report_marker_error(PyObject *marker) { } static void -apply_mark(Line *line, const attrs_type mark, index_type *cell_pos, unsigned int *match_pos) { -#define MARK { line->gpu_cells[x].attrs &= ATTRS_MASK_WITHOUT_MARK; line->gpu_cells[x].attrs |= mark; } +apply_mark(Line *line, const uint16_t mark, index_type *cell_pos, unsigned int *match_pos) { +#define MARK { line->gpu_cells[x].attrs.bits.mark = mark; } index_type x = *cell_pos; MARK; (*match_pos)++; @@ -744,7 +733,7 @@ apply_mark(Line *line, const attrs_type mark, index_type *cell_pos, unsigned int num_cells_to_skip_for_tab--; MARK; } - } else if ((line->gpu_cells[x].attrs & WIDTH_MASK) > 1 && x + 1 < line->xnum && !line->cpu_cells[x+1].ch) { + } else if ((line->gpu_cells[x].attrs.bits.width) > 1 && x + 1 < line->xnum && !line->cpu_cells[x+1].ch) { x++; MARK; } else { @@ -773,28 +762,28 @@ apply_marker(PyObject *marker, Line *line, const PyObject *text) { while (match_pos < l && x < line->xnum) { apply_mark(line, 0, &x, &match_pos); } - attrs_type am = (col & MARK_MASK) << MARK_SHIFT; + uint16_t am = (col & MARK_MASK); while(x < line->xnum && match_pos <= r) { apply_mark(line, am, &x, &match_pos); } } Py_DECREF(iter); - while(x < line->xnum) line->gpu_cells[x++].attrs &= ATTRS_MASK_WITHOUT_MARK; + while(x < line->xnum) line->gpu_cells[x++].attrs.bits.mark = 0; if (PyErr_Occurred()) report_marker_error(marker); } void mark_text_in_line(PyObject *marker, Line *line) { if (!marker) { - for (index_type i = 0; i < line->xnum; i++) line->gpu_cells[i].attrs &= ATTRS_MASK_WITHOUT_MARK; + for (index_type i = 0; i < line->xnum; i++) line->gpu_cells[i].attrs.bits.mark = 0; return; } PyObject *text = line_as_unicode(line, false); if (PyUnicode_GET_LENGTH(text) > 0) { apply_marker(marker, line, text); } else { - for (index_type i = 0; i < line->xnum; i++) line->gpu_cells[i].attrs &= ATTRS_MASK_WITHOUT_MARK; + for (index_type i = 0; i < line->xnum; i++) line->gpu_cells[i].attrs.bits.mark = 0; } Py_DECREF(text); } diff --git a/kitty/lineops.h b/kitty/lineops.h index e2f965949..9e2ead8d9 100644 --- a/kitty/lineops.h +++ b/kitty/lineops.h @@ -8,13 +8,16 @@ #include "data-types.h" -static inline void -set_attribute_on_line(GPUCell *cells, uint32_t shift, uint32_t val, index_type xnum) { +#define set_attribute_on_line(cells, which, val, xnum) { \ + for (index_type i__ = 0; i__ < xnum; i__++) cells[i__].attrs.bits.which = val; } + +static inline bool +set_named_attribute_on_line(GPUCell *cells, const char* which, uint16_t val, index_type xnum) { // Set a single attribute on all cells in the line - attrs_type mask = shift == DECORATION_SHIFT ? 3 : 1; - attrs_type aval = (val & mask) << shift; - mask = ~(mask << shift); - for (index_type i = 0; i < xnum; i++) cells[i].attrs = (cells[i].attrs & mask) | aval; +#define s(q) if (strcmp(#q, which) == 0) { set_attribute_on_line(cells, q, val, xnum); return true; } + s(reverse); s(width); s(strike); s(dim); s(mark); s(bold); s(italic); s(decoration); + return false; +#undef s } @@ -28,7 +31,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) { - for (index_type i = 0; i < xnum; i++) { cpu_cells[i].ch = ch; cpu_cells[i].hyperlink_id = 0; gpu_cells[i].attrs = 1; } + const CellAttrs empty = {.bits={.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; } } } @@ -37,7 +41,7 @@ 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--; - if (xlimit < line->xnum && (line->gpu_cells[xlimit > 0 ? xlimit - 1 : xlimit].attrs & WIDTH_MASK) == 2) xlimit++; + if (xlimit < line->xnum && line->gpu_cells[xlimit > 0 ? xlimit - 1 : xlimit].attrs.bits.width == 2) xlimit++; } return xlimit; } @@ -59,10 +63,12 @@ left_shift_line(Line *line, index_type at, index_type num) { for (index_type i = at; i < line->xnum - num; i++) { COPY_CELL(line, i + num, line, i); } - if (at < line->xnum && ((line->gpu_cells[at].attrs) & WIDTH_MASK) != 1) { + const CellAttrs empty = {.bits={.width=1}}; + const CellAttrs zero = {0}; + if (at < line->xnum && line->gpu_cells[at].attrs.bits.width != 1) { line->cpu_cells[at].ch = BLANK_CHAR; line->cpu_cells[at].hyperlink_id = 0; - line->gpu_cells[at].attrs = BLANK_CHAR ? 1 : 0; + line->gpu_cells[at].attrs = BLANK_CHAR ? empty : zero; clear_sprite_position(line->gpu_cells[at]); } } @@ -95,7 +101,6 @@ void linebuf_clear_line(LineBuf *self, index_type y, bool clear_attrs); void linebuf_insert_lines(LineBuf *self, unsigned int num, unsigned int y, unsigned int bottom); void linebuf_delete_lines(LineBuf *self, index_type num, index_type y, index_type bottom); void linebuf_copy_line_to(LineBuf *, Line *, index_type); -void linebuf_set_attribute(LineBuf *, unsigned int , unsigned int ); void linebuf_rewrap(LineBuf *self, LineBuf *other, index_type *, index_type *, HistoryBuf *, index_type *, index_type *, index_type *, index_type *, ANSIBuf*); void linebuf_mark_line_dirty(LineBuf *self, index_type y); void linebuf_mark_line_clean(LineBuf *self, index_type y); @@ -114,5 +119,5 @@ void historybuf_mark_line_dirty(HistoryBuf *self, index_type y); void historybuf_refresh_sprite_positions(HistoryBuf *self); void historybuf_clear(HistoryBuf *self); void mark_text_in_line(PyObject *marker, Line *line); -bool line_has_mark(Line *, attrs_type mark); +bool line_has_mark(Line *, uint16_t mark); PyObject* as_text_generic(PyObject *args, void *container, get_line_func get_line, index_type lines, ANSIBuf *ansibuf); diff --git a/kitty/screen.c b/kitty/screen.c index cabdda40c..d90068328 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -594,24 +594,24 @@ draw_combining_char(Screen *self, char_type ch) { 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; - if ((gpu_cell->attrs & WIDTH_MASK) != 2 && cpu_cell->cc_idx[0] == VS16 && is_emoji_presentation_base(cpu_cell->ch)) { + if (gpu_cell->attrs.bits.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 = (gpu_cell->attrs & !WIDTH_MASK) | 2; + gpu_cell->attrs.bits.width = 2; if (xpos == self->columns - 1) move_widened_char(self, cpu_cell, gpu_cell, xpos, ypos); else self->cursor->x++; } } else if (ch == 0xfe0e) { CPUCell *cpu_cell = self->linebuf->line->cpu_cells + xpos; GPUCell *gpu_cell = self->linebuf->line->gpu_cells + xpos; - if ((gpu_cell->attrs & WIDTH_MASK) == 0 && cpu_cell->ch == 0 && xpos > 0) { + if (gpu_cell->attrs.bits.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; } - if ((gpu_cell->attrs & WIDTH_MASK) == 2 && cpu_cell->cc_idx[0] == VS15 && is_emoji_presentation_base(cpu_cell->ch)) { - gpu_cell->attrs = (gpu_cell->attrs & !WIDTH_MASK) | 1; + if (gpu_cell->attrs.bits.width == 2 && cpu_cell->cc_idx[0] == VS15 && is_emoji_presentation_base(cpu_cell->ch)) { + gpu_cell->attrs.bits.width = 1; } } } @@ -3230,7 +3230,7 @@ marked_cells(Screen *self, PyObject *o UNUSED) { linebuf_init_line(self->linebuf, y); for (index_type x = 0; x < self->columns; x++) { GPUCell *gpu_cell = self->linebuf->line->gpu_cells + x; - unsigned int mark = (gpu_cell->attrs >> MARK_SHIFT) & MARK_MASK; + const unsigned int mark = gpu_cell->attrs.bits.mark; if (mark) { PyObject *t = Py_BuildValue("III", x, y, mark); if (!t) { Py_DECREF(ans); return NULL; } diff --git a/kitty_tests/datatypes.py b/kitty_tests/datatypes.py index 26430fb51..fd6daece6 100644 --- a/kitty_tests/datatypes.py +++ b/kitty_tests/datatypes.py @@ -7,7 +7,7 @@ import tempfile from kitty.config import build_ansi_color_table, defaults from kitty.fast_data_types import ( - REVERSE, ColorProfile, Cursor as C, HistoryBuf, LineBuf, + ColorProfile, Cursor as C, HistoryBuf, LineBuf, parse_input_from_terminal, truncate_point_for_length, wcswidth, wcwidth ) from kitty.rgb import to_color @@ -51,7 +51,7 @@ class TestDataTypes(BaseTest): self.ae(new.line(0), old.line(1)) new.clear() self.ae(str(new.line(0)), '') - old.set_attribute(REVERSE, False) + old.set_attribute('reverse', False) for y in range(old.ynum): for x in range(old.xnum): l0 = old.line(y)