Improve performance of processing wide chars
Store multi cell data in the CPUCell rather than in TextCache. This sends the CPUCell size back to 12 but in benchmarks ASCII performance is untouched and Unicode performace goes back to what it was before multicell
This commit is contained in:
parent
21871dcd58
commit
35946f9386
9 changed files with 127 additions and 195 deletions
|
|
@ -481,9 +481,9 @@ face_has_codepoint(const void* face, char_type cp) {
|
|||
}
|
||||
|
||||
static bool
|
||||
has_emoji_presentation(const ListOfChars *lc) {
|
||||
if (!lc->is_multicell || !lc->is_topleft || !lc->count) return false;
|
||||
return is_emoji(lc->chars[0]) && (lc->count == 1 || lc->chars[1] != VS15);
|
||||
has_emoji_presentation(const CPUCell *c, const ListOfChars *lc) {
|
||||
if (!c->is_multicell || c->x || c->y || !lc->count) return false;
|
||||
return is_emoji(lc->chars[0]) && (lc->count == 1 || lc->chars[1] != VS15);
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
@ -581,10 +581,10 @@ chars_as_utf8(const ListOfChars *lc, char *buf, char_type zero_char) {
|
|||
}
|
||||
|
||||
static ssize_t
|
||||
fallback_font(FontGroup *fg, const GPUCell *gpu_cell, const ListOfChars *lc) {
|
||||
fallback_font(FontGroup *fg, const CPUCell *cpu_cell, 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(lc);
|
||||
bool emoji_presentation = has_emoji_presentation(cpu_cell, lc);
|
||||
char style = emoji_presentation ? 'a' : 'A';
|
||||
if (bold) style += italic ? 3 : 2; else style += italic ? 1 : 0;
|
||||
char cell_text[4 * 32] = {style};
|
||||
|
|
@ -640,7 +640,7 @@ START_ALLOW_CASE_RANGE
|
|||
case 0xf5d0 ... 0xf60d: // branch drawing characters
|
||||
return BOX_FONT;
|
||||
default:
|
||||
*is_emoji_presentation = has_emoji_presentation(lc);
|
||||
*is_emoji_presentation = has_emoji_presentation(cpu_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)) {
|
||||
|
|
@ -655,7 +655,7 @@ START_ALLOW_CASE_RANGE
|
|||
}
|
||||
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, false, lc)) { *is_main_font = true; return ans; }
|
||||
return fallback_font(fg, gpu_cell, lc);
|
||||
return fallback_font(fg, cpu_cell, gpu_cell, lc);
|
||||
}
|
||||
END_ALLOW_CASE_RANGE
|
||||
}
|
||||
|
|
@ -776,7 +776,7 @@ static void
|
|||
render_box_cell(FontGroup *fg, RunFont rf, CPUCell *cpu_cell, GPUCell *gpu_cell, const TextCache *tc) {
|
||||
int error = 0;
|
||||
// We need to render multicell chars for multicell_y > 0 cell_first_char() returns 0 for such cells
|
||||
char_type ch = cpu_cell->is_multicell ? tc_first_char_at_index(tc, cpu_cell->ch_or_idx) : cell_first_char(cpu_cell, tc);
|
||||
char_type ch = cpu_cell->ch_is_idx ? tc_first_char_at_index(tc, cpu_cell->ch_or_idx) : cpu_cell->ch_or_idx;
|
||||
glyph_index glyph = box_glyph_id(ch);
|
||||
ensure_glyph_render_scratch_space(rf.scale);
|
||||
bool all_rendered = true;
|
||||
|
|
@ -824,7 +824,7 @@ load_hb_buffer(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_
|
|||
while (num_cells) {
|
||||
for (num = 0; num_cells; first_cpu_cell++, first_gpu_cell++, num_cells--) {
|
||||
text_in_cell(first_cpu_cell, tc, &lc);
|
||||
if (lc.is_multicell && !lc.is_topleft) continue;
|
||||
if (first_cpu_cell->is_multicell && (first_cpu_cell->x + first_cpu_cell->y)) continue;
|
||||
if (lc.count + num > arraysz(shape_buffer)) break;
|
||||
memcpy(shape_buffer + num, lc.chars, lc.count * sizeof(shape_buffer[0]));
|
||||
num += lc.count;
|
||||
|
|
@ -866,7 +866,7 @@ render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPU
|
|||
|
||||
ensure_canvas_can_fit(fg, num_cells + 1, rf.scale);
|
||||
text_in_cell(cpu_cells, tc, global_glyph_render_scratch.lc);
|
||||
bool was_colored = has_emoji_presentation(global_glyph_render_scratch.lc);
|
||||
bool was_colored = has_emoji_presentation(cpu_cells, global_glyph_render_scratch.lc);
|
||||
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();
|
||||
|
||||
|
|
@ -982,8 +982,7 @@ check_cell_consumed(CellData *cell_data, CPUCell *last_cpu_cell, const TextCache
|
|||
if (cell_data->codepoints_consumed >= cell_data->num_codepoints) {
|
||||
uint16_t width = 1;
|
||||
if (cell_data->cpu_cell->is_multicell) {
|
||||
MultiCellData mcd = cell_multicell_data(cell_data->cpu_cell, tc);
|
||||
width = mcd.width * mcd.scale;
|
||||
width = cell_data->cpu_cell->width * cell_data->cpu_cell->scale;
|
||||
}
|
||||
cell_data->cpu_cell += width;
|
||||
cell_data->gpu_cell += width;
|
||||
|
|
@ -1304,20 +1303,20 @@ collapse_pua_space_ligature(index_type num_cells) {
|
|||
}
|
||||
|
||||
static bool
|
||||
is_group_calt_ligature(const Group *group, const TextCache *tc) {
|
||||
is_group_calt_ligature(const Group *group) {
|
||||
if (group->num_cells < 2 || !group->has_special_glyph) return false;
|
||||
const CPUCell *first_cell = G(first_cpu_cell) + group->first_cell_idx;
|
||||
return !first_cell->is_multicell || cell_multicell_data(first_cell, tc).width == 1;
|
||||
return !first_cell->is_multicell || first_cell->width == 1;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
split_run_at_offset(index_type cursor_offset, index_type *left, index_type *right, const TextCache *tc) {
|
||||
split_run_at_offset(index_type cursor_offset, index_type *left, index_type *right) {
|
||||
*left = 0; *right = 0;
|
||||
for (unsigned idx = 0; idx < G(group_idx) + 1; idx++) {
|
||||
Group *group = G(groups) + idx;
|
||||
if (group->first_cell_idx <= cursor_offset && cursor_offset < group->first_cell_idx + group->num_cells) {
|
||||
if (is_group_calt_ligature(group, tc)) {
|
||||
if (is_group_calt_ligature(group)) {
|
||||
// likely a calt ligature
|
||||
*left = group->first_cell_idx; *right = group->first_cell_idx + group->num_cells;
|
||||
}
|
||||
|
|
@ -1354,8 +1353,7 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
|
|||
while(num < line->xnum && cell_has_text(line->cpu_cells + num)) {
|
||||
index_type width = 1;
|
||||
if ((c = line->cpu_cells + num)->is_multicell) {
|
||||
MultiCellData mcd = cell_multicell_data(c, line->text_cache);
|
||||
width = mcd.width * mcd.scale;
|
||||
width = c->width * c->scale;
|
||||
}
|
||||
num += width;
|
||||
}
|
||||
|
|
@ -1400,7 +1398,7 @@ render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, inde
|
|||
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, tc);
|
||||
split_run_at_offset(cursor_offset, &left, &right);
|
||||
if (right > left) {
|
||||
if (left) {
|
||||
shape_run(first_cpu_cell, first_gpu_cell, left, &fg->fonts[rf.font_idx], false, tc);
|
||||
|
|
@ -1473,24 +1471,22 @@ 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;
|
||||
MultiCellData mcd;
|
||||
for (i=0, first_cell_in_run=0; i < line->xnum; i++) {
|
||||
cell_font = basic_font;
|
||||
CPUCell *cpu_cell = line->cpu_cells + i;
|
||||
if (cpu_cell->is_multicell) {
|
||||
mcd = cell_multicell_data(cpu_cell, line->text_cache);
|
||||
if (cpu_cell->x) {
|
||||
i += mcd_x_limit(mcd) - cpu_cell->x - 1;
|
||||
i += mcd_x_limit(cpu_cell) - cpu_cell->x - 1;
|
||||
continue;
|
||||
}
|
||||
cell_font.scale = mcd.scale; cell_font.subscale = mcd.subscale; cell_font.vertical_align = mcd.vertical_align;
|
||||
cell_font.scale = cpu_cell->scale; cell_font.subscale = cpu_cell->subscale; cell_font.vertical_align = cpu_cell->vertical_align;
|
||||
cell_font.multicell_y = cpu_cell->y;
|
||||
}
|
||||
text_in_cell(cpu_cell, line->text_cache, lc);
|
||||
bool is_main_font, is_emoji_presentation;
|
||||
GPUCell *gpu_cell = line->gpu_cells + i;
|
||||
cell_font.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];
|
||||
cell_font.font_idx = font_for_cell(fg, cpu_cell, gpu_cell, &is_main_font, &is_emoji_presentation, line->text_cache, lc);
|
||||
if (
|
||||
cell_font.font_idx != MISSING_FONT &&
|
||||
((!is_main_font && !is_emoji_presentation && is_symbol(first_ch)) || (cell_font.font_idx != BOX_FONT && (is_private_use(first_ch))) || is_non_emoji_dingbat(first_ch))
|
||||
|
|
@ -1831,14 +1827,14 @@ get_fallback_font(PyObject UNUSED *self, PyObject *args) {
|
|||
PyObject *text;
|
||||
int bold, italic;
|
||||
if (!PyArg_ParseTuple(args, "Upp", &text, &bold, &italic)) return NULL;
|
||||
GPUCell gpu_cell = {0};
|
||||
GPUCell gpu_cell = {0}; CPUCell cpu_cell = {0};
|
||||
RAII_ListOfChars(lc); lc.count = PyUnicode_GET_LENGTH(text);
|
||||
ensure_space_for_chars(&lc, lc.count);
|
||||
if (!PyUnicode_AsUCS4(text, lc.chars, lc.capacity, 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, &gpu_cell, &lc);
|
||||
ssize_t ans = fallback_font(fg, &cpu_cell, &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;
|
||||
|
|
|
|||
|
|
@ -599,17 +599,17 @@ restitch(Line *dest, index_type at, LineBuf *src, const index_type src_y, TrackC
|
|||
bool found_text = false;
|
||||
while (at < dest->xnum && x < src->xnum) {
|
||||
if (!found_text && cell_has_text(&cp[x])) found_text = true;
|
||||
if (gp[x].attrs.width == 2) {
|
||||
if (dest->xnum - at > 1) {
|
||||
dest->cpu_cells[at] = cp[x];
|
||||
dest->gpu_cells[at++] = gp[x++];
|
||||
dest->cpu_cells[at] = cp[x];
|
||||
dest->gpu_cells[at] = gp[x];
|
||||
at++; x++;
|
||||
if (cp[x].is_multicell) {
|
||||
// TODO: handle multiline chars
|
||||
if (dest->xnum - at > cp[x].width) {
|
||||
for (index_type i = 0; i < cp[x].width; i++) {
|
||||
dest->cpu_cells[at] = cp[x];
|
||||
dest->gpu_cells[at++] = gp[x++];
|
||||
}
|
||||
} else {
|
||||
dest->cpu_cells[at] = (CPUCell){0};
|
||||
dest->gpu_cells[at] = (GPUCell){0};
|
||||
at++;
|
||||
at = dest->xnum;
|
||||
}
|
||||
} else {
|
||||
dest->cpu_cells[at] = cp[x];
|
||||
|
|
@ -634,7 +634,7 @@ restitch(Line *dest, index_type at, LineBuf *src, const index_type src_y, TrackC
|
|||
}
|
||||
y++;
|
||||
}
|
||||
dest->gpu_cells[dest->xnum - 1].attrs.next_char_was_wrapped = last_char_has_wrapped_flag;
|
||||
dest->cpu_cells[dest->xnum - 1].next_char_was_wrapped = last_char_has_wrapped_flag;
|
||||
// remove the copied lines and cells from src
|
||||
if (num_of_lines_removed) {
|
||||
for (index_type i = 0; i < num_of_lines_removed; i++) {
|
||||
|
|
|
|||
54
kitty/line.c
54
kitty/line.c
|
|
@ -54,21 +54,21 @@ nonnegative_integer_as_utf32(unsigned num, ANSIBuf *output) {
|
|||
}
|
||||
|
||||
static unsigned
|
||||
write_multicell_ansi_prefix(MultiCellData mcd, ANSIBuf *output) {
|
||||
write_multicell_ansi_prefix(const CPUCell *mcd, ANSIBuf *output) {
|
||||
unsigned pos = output->len;
|
||||
ensure_space_for(output, buf, output->buf[0], output->len + 128, capacity, 2048, false);
|
||||
#define w(x) output->buf[output->len++] = x
|
||||
w(0x1b); w(']');
|
||||
for (unsigned i = 0; i < sizeof(xstr(TEXT_SIZE_CODE)) - 1; i++) w(xstr(TEXT_SIZE_CODE)[i]);
|
||||
w(';');
|
||||
if (mcd.width > 1) {
|
||||
w('w'); w('='); nonnegative_integer_as_utf32(mcd.width, output); w(':');
|
||||
if (mcd->width > 1) {
|
||||
w('w'); w('='); nonnegative_integer_as_utf32(mcd->width, output); w(':');
|
||||
}
|
||||
if (mcd.scale > 1) {
|
||||
w('s'); w('='); nonnegative_integer_as_utf32(mcd.scale, output); w(':');
|
||||
if (mcd->scale > 1) {
|
||||
w('s'); w('='); nonnegative_integer_as_utf32(mcd->scale, output); w(':');
|
||||
}
|
||||
if (mcd.subscale) {
|
||||
w('S'); w('='); nonnegative_integer_as_utf32(mcd.subscale, output); w(':');
|
||||
if (mcd->subscale) {
|
||||
w('S'); w('='); nonnegative_integer_as_utf32(mcd->subscale, output); w(':');
|
||||
}
|
||||
if (output->buf[output->len - 1] == ':') output->len--;
|
||||
w(';');
|
||||
|
|
@ -78,18 +78,25 @@ write_multicell_ansi_prefix(MultiCellData mcd, ANSIBuf *output) {
|
|||
|
||||
static unsigned
|
||||
text_in_cell_ansi(const CPUCell *c, TextCache *tc, ANSIBuf *output) {
|
||||
if (c->ch_is_idx) {
|
||||
if (!c->is_multicell) return tc_chars_at_index_ansi(tc, c->ch_or_idx, output);
|
||||
unsigned n = 0;
|
||||
if (c->is_multicell) {
|
||||
if (c->x || c->y) return 0;
|
||||
MultiCellData mcd = cell_multicell_data(c, tc);
|
||||
unsigned n = write_multicell_ansi_prefix(mcd, output);
|
||||
n += tc_chars_at_index_ansi(tc, c->ch_or_idx, output) - 1;
|
||||
output->buf[output->len - 1] = '\a';
|
||||
return n;
|
||||
n = write_multicell_ansi_prefix(c, 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;
|
||||
if (c->ch_is_idx) {
|
||||
n += tc_chars_at_index_ansi(tc, c->ch_or_idx, output);
|
||||
} else {
|
||||
ensure_space_for(output, buf, output->buf[0], output->len + 2, capacity, 2048, false);
|
||||
if (c->ch_or_idx) {
|
||||
output->buf[output->len++] = c->ch_or_idx;
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
if (c->is_multicell) {
|
||||
output->buf[output->len++] = '\a';
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -326,7 +333,7 @@ unicode_in_range(const Line *self, const index_type start, const index_type limi
|
|||
global_unicode_in_range_buf.capacity = ns; global_unicode_in_range_buf.chars = np;
|
||||
lc.chars = global_unicode_in_range_buf.chars + n; lc.capacity = global_unicode_in_range_buf.capacity - n;
|
||||
}
|
||||
if (lc.is_multicell && !lc.is_topleft) continue;
|
||||
if (self->cpu_cells[i].is_multicell && (self->cpu_cells[i].x || self->cpu_cells[i].y)) continue;
|
||||
if (!lc.chars[0]) {
|
||||
if (skip_zero_cells) continue;
|
||||
lc.chars[0] = ' ';
|
||||
|
|
@ -449,9 +456,7 @@ line_as_ansi(Line *self, ANSIBuf *output, const GPUCell** prev_cell, index_type
|
|||
}
|
||||
|
||||
unsigned n = text_in_cell_ansi(self->cpu_cells + pos, self->text_cache, output);
|
||||
if (output->buf[output->len - n] == 0) {
|
||||
output->buf[output->len - n] = ' ';
|
||||
}
|
||||
if (output->buf[output->len - n] == 0) output->buf[output->len - n] = ' ';
|
||||
|
||||
if (output->buf[output->len - n] == '\t') {
|
||||
unsigned num_cells_to_skip_for_tab = 0;
|
||||
|
|
@ -516,7 +521,7 @@ width(Line *self, PyObject *val) {
|
|||
const CPUCell *c = self->cpu_cells + x;
|
||||
if (!cell_has_text(c)) return 0;
|
||||
unsigned long ans = 1;
|
||||
if (c->is_multicell) ans = c->x || c->y ? 0 : cell_multicell_data(c, self->text_cache).width;
|
||||
if (c->is_multicell) ans = c->x || c->y ? 0 : c->width;
|
||||
return PyLong_FromUnsignedLong(ans);
|
||||
}
|
||||
|
||||
|
|
@ -678,7 +683,7 @@ line_get_char(Line *self, index_type at) {
|
|||
if (self->cpu_cells[at].ch_is_idx) {
|
||||
RAII_ListOfChars(lc);
|
||||
text_in_cell(self->cpu_cells + at, self->text_cache, &lc);
|
||||
if (lc.is_multicell && !lc.is_topleft) return 0;
|
||||
if (self->cpu_cells[at].is_multicell && (self->cpu_cells[at].x || self->cpu_cells[at].y)) return 0;
|
||||
return lc.chars[0];
|
||||
} else return self->cpu_cells[at].ch_or_idx;
|
||||
}
|
||||
|
|
@ -839,9 +844,8 @@ apply_mark(Line *line, const uint16_t mark, index_type *cell_pos, unsigned int *
|
|||
MARK;
|
||||
}
|
||||
} else if (line->cpu_cells[x].is_multicell) {
|
||||
MultiCellData mcd = {.val=lc.chars[lc.count]};
|
||||
*match_pos += lc.count - 1;
|
||||
index_type x_limit = MIN(line->xnum, mcd_x_limit(mcd));
|
||||
index_type x_limit = MIN(line->xnum, mcd_x_limit(line->cpu_cells + x));
|
||||
for (; x < x_limit; x++) { MARK; }
|
||||
x--;
|
||||
} else {
|
||||
|
|
|
|||
50
kitty/line.h
50
kitty/line.h
|
|
@ -17,7 +17,7 @@
|
|||
// TODO: Cursor rendering over multicell
|
||||
// TODO: Test the escape codes to delete and insert characters and lines with multicell
|
||||
// TODO: Handle replay of dumped graphics_command and multicell_command
|
||||
// TODO: Handle rewrap of multiline chars
|
||||
// TODO: Handle rewrap and restitch of multiline chars
|
||||
// TODO: Handle rewrap when a character is too wide/tall to fit on resized screen
|
||||
// TODO: Test serialization to ansi only using escape code for explicitly set multicells
|
||||
// TODO: Test rendering of box drawing at various scales and subscales and alignments
|
||||
|
|
@ -54,38 +54,29 @@ typedef struct {
|
|||
} GPUCell;
|
||||
static_assert(sizeof(GPUCell) == 20, "Fix the ordering of GPUCell");
|
||||
|
||||
typedef union MultiCellData {
|
||||
struct {
|
||||
char_type scale: 3;
|
||||
char_type width: 3;
|
||||
char_type subscale: 2;
|
||||
char_type vertical_align: 3;
|
||||
char_type explicitly_set: 1;
|
||||
char_type : 19;
|
||||
char_type msb : 1;
|
||||
};
|
||||
char_type val;
|
||||
} MultiCellData;
|
||||
static_assert(sizeof(MultiCellData) == sizeof(char_type), "Fix the ordering of MultiCellData");
|
||||
|
||||
typedef union CPUCell {
|
||||
struct {
|
||||
char_type ch_or_idx: sizeof(char_type) * 8 - 1;
|
||||
char_type ch_is_idx: 1;
|
||||
char_type hyperlink_id: sizeof(hyperlink_id_type) * 8;
|
||||
char_type next_char_was_wrapped : 1;
|
||||
char_type is_multicell : 1;
|
||||
char_type explicitly_set: 1;
|
||||
char_type x : 8;
|
||||
char_type y : 4;
|
||||
char_type is_multicell : 1;
|
||||
char_type next_char_was_wrapped : 1;
|
||||
char_type : 2;
|
||||
char_type subscale: 2;
|
||||
char_type scale: 3;
|
||||
char_type width: 3;
|
||||
char_type vertical_align: 3;
|
||||
char_type : 21;
|
||||
};
|
||||
struct {
|
||||
char_type ch_and_idx: sizeof(char_type) * 8;
|
||||
char_type : sizeof(hyperlink_id_type) * 8;
|
||||
char_type : 16;
|
||||
char_type : 32;
|
||||
char_type : 32;
|
||||
};
|
||||
} CPUCell;
|
||||
static_assert(sizeof(CPUCell) == sizeof(uint64_t), "Fix the ordering of CPUCell");
|
||||
static_assert(sizeof(CPUCell) == 12, "Fix the ordering of CPUCell");
|
||||
|
||||
typedef union LineAttrs {
|
||||
struct {
|
||||
|
|
@ -130,21 +121,12 @@ static inline unsigned num_codepoints_in_cell(const CPUCell *c, const TextCache
|
|||
} else ans = c->ch_or_idx ? 1 : 0;
|
||||
return ans;
|
||||
}
|
||||
static inline MultiCellData cell_multicell_data(const CPUCell *c, const TextCache *tc) {
|
||||
return (MultiCellData){.val = tc_last_char_at_index(tc, c->ch_or_idx)};
|
||||
}
|
||||
static inline unsigned mcd_x_limit(MultiCellData mcd) { return mcd.scale * mcd.width; }
|
||||
static inline unsigned mcd_x_limit(const CPUCell* mcd) { return mcd->scale * mcd->width; }
|
||||
|
||||
static inline void
|
||||
text_in_cell(const CPUCell *c, const TextCache *tc, ListOfChars *ans) {
|
||||
ans->is_multicell = false;
|
||||
if (c->ch_is_idx) {
|
||||
tc_chars_at_index(tc, c->ch_or_idx, ans);
|
||||
if (c->is_multicell) {
|
||||
ans->is_multicell = true;
|
||||
ans->is_topleft = c->x + c->y == 0;
|
||||
if (ans->count > 0) { ans->count--; }
|
||||
}
|
||||
} else {
|
||||
ans->count = 1;
|
||||
ans->chars[0] = c->ch_or_idx;
|
||||
|
|
@ -153,14 +135,8 @@ text_in_cell(const CPUCell *c, const TextCache *tc, ListOfChars *ans) {
|
|||
|
||||
static inline bool
|
||||
text_in_cell_without_alloc(const CPUCell *c, const TextCache *tc, ListOfChars *ans) {
|
||||
ans->is_multicell = false;
|
||||
if (c->ch_is_idx) {
|
||||
if (!tc_chars_at_index_without_alloc(tc, c->ch_or_idx, ans)) return false;
|
||||
if (c->is_multicell) {
|
||||
ans->is_multicell = true;
|
||||
ans->is_topleft = c->x + c->y == 0;
|
||||
if (ans->count > 0) { ans->count--; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
ans->count = 1;
|
||||
|
|
|
|||
143
kitty/screen.c
143
kitty/screen.c
|
|
@ -650,10 +650,9 @@ static void
|
|||
nuke_multicell_char_at(Screen *self, index_type x_, index_type y_, bool replace_with_spaces) {
|
||||
CPUCell *cp; GPUCell *gp;
|
||||
linebuf_init_cells(self->linebuf, y_, &cp, &gp);
|
||||
MultiCellData mcd = cell_multicell_data(cp + x_, self->text_cache);
|
||||
index_type y_max_limit = MIN(self->lines, y_ + mcd.scale);
|
||||
index_type y_max_limit = MIN(self->lines, y_ + cp[x_].scale);
|
||||
while (cp[x_].x && x_ > 0) x_--;
|
||||
index_type x_limit = MIN(self->columns, x_ + mcd_x_limit(mcd));
|
||||
index_type x_limit = MIN(self->columns, x_ + mcd_x_limit(&cp[x_]));
|
||||
char_type ch = replace_with_spaces ? ' ' : 0;
|
||||
for (index_type y = y_; y < y_max_limit; y++) {
|
||||
linebuf_init_cells(self->linebuf, y, &cp, &gp);
|
||||
|
|
@ -674,7 +673,7 @@ nuke_multiline_char_intersecting_with(Screen *self, index_type x_start, index_ty
|
|||
CPUCell *cp; GPUCell *gp;
|
||||
linebuf_init_cells(self->linebuf, y, &cp, &gp);
|
||||
for (index_type x = x_start; x < x_limit; x++) {
|
||||
if (cp[x].is_multicell && cell_multicell_data(cp + x, self->text_cache).scale > 1) nuke_multicell_char_at(self, x, y, replace_with_spaces);
|
||||
if (cp[x].is_multicell && cp[x].scale > 1) nuke_multicell_char_at(self, x, y, replace_with_spaces);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -704,8 +703,7 @@ nuke_split_multicell_char_at_right_boundary(Screen *self, index_type x, index_ty
|
|||
CPUCell *cp = linebuf_cpu_cells_for_line(self->linebuf, y);
|
||||
CPUCell *c = cp + x;
|
||||
if (c->is_multicell) {
|
||||
MultiCellData mcd = cell_multicell_data(c, self->text_cache);
|
||||
unsigned max_x = mcd_x_limit(mcd) - 1;
|
||||
unsigned max_x = mcd_x_limit(c) - 1;
|
||||
if (c->x < max_x) {
|
||||
nuke_multicell_char_at(self, x, y, replace_with_spaces);
|
||||
}
|
||||
|
|
@ -720,8 +718,7 @@ nuke_incomplete_single_line_multicell_chars_in_range(
|
|||
linebuf_init_cells(self->linebuf, y, &cpu_cells, &gpu_cells);
|
||||
for (index_type x = start; x < limit; x++) {
|
||||
if (cpu_cells[x].is_multicell) {
|
||||
MultiCellData mcd = cell_multicell_data(cpu_cells + x, self->text_cache);
|
||||
index_type mcd_x_limit = x + mcd.width - cpu_cells[x].x;
|
||||
index_type mcd_x_limit = x + cpu_cells[x].width - cpu_cells[x].x;
|
||||
if (cpu_cells[x].x || mcd_x_limit > limit) nuke_in_line(cpu_cells, gpu_cells, x, MIN(mcd_x_limit, limit), replace_with_spaces ? ' ': 0);
|
||||
x = mcd_x_limit;
|
||||
}
|
||||
|
|
@ -749,25 +746,19 @@ static bool
|
|||
halve_multicell_width(Screen *self, index_type x_, index_type y_) {
|
||||
CPUCell *cp; GPUCell *gp;
|
||||
linebuf_init_cells(self->linebuf, y_, &cp, &gp);
|
||||
MultiCellData mcd = cell_multicell_data(cp + x_, self->text_cache);
|
||||
int y_min_limit = -1;
|
||||
if (self->linebuf == self->main_linebuf) y_min_limit = -(self->historybuf->count + 1);
|
||||
int expected_y_min_limit = ((int)y_) - mcd.scale;
|
||||
int expected_y_min_limit = ((int)y_) - cp[x_].scale;
|
||||
if (expected_y_min_limit < y_min_limit) return false;
|
||||
y_min_limit = expected_y_min_limit;
|
||||
MultiCellData new_mcd = mcd; new_mcd.width = mcd.width / 2;
|
||||
unsigned new_width = cp[x_].width / 2;
|
||||
while (cp[x_].x && x_ > 0) x_--;
|
||||
index_type x_limit = MIN(self->columns, x_ + mcd_x_limit(mcd));
|
||||
index_type x_limit = MIN(self->columns, x_ + mcd_x_limit(&cp[x_]));
|
||||
index_type half_x_limit = x_limit / 2;
|
||||
int y_max_limit = MIN(self->lines, y_ + mcd.scale);
|
||||
RAII_ListOfChars(lc);
|
||||
text_in_cell(cp + x_, self->text_cache, &lc);
|
||||
lc.chars[lc.count++] = new_mcd.val;
|
||||
cell_set_chars(cp + x_, self->text_cache, &lc);
|
||||
char_type idx = cp[x_].ch_or_idx;
|
||||
int y_max_limit = MIN(self->lines, y_ + cp[x_].scale);
|
||||
for (int y = y_min_limit + 1; y < y_max_limit; y++) {
|
||||
Line *line = range_line_(self, y); cp = line->cpu_cells; gp = line->gpu_cells;
|
||||
for (index_type x = 0; x < half_x_limit; x++) cp[x].ch_or_idx = idx;
|
||||
for (index_type x = 0; x < half_x_limit; x++) cp[x].width = new_width;
|
||||
for (index_type x = half_x_limit; x < x_limit; x++) {
|
||||
cp[x] = (CPUCell){0}; clear_sprite_position(gp[x]);
|
||||
}
|
||||
|
|
@ -823,29 +814,21 @@ static bool
|
|||
add_combining_char(Screen *self, char_type ch, index_type x, index_type y) {
|
||||
CPUCell *cpu_cells = linebuf_cpu_cells_for_line(self->linebuf, y);
|
||||
CPUCell *cell = cpu_cells + x;
|
||||
if (!cell_has_text(cell)) return false; // don't allow adding combining chars to a null cell
|
||||
if (!cell_has_text(cell) || (cell->is_multicell && cell->y)) return false; // don't allow adding combining chars to a null cell
|
||||
text_in_cell(cell, self->text_cache, self->lc);
|
||||
ensure_space_for_chars(self->lc, self->lc->count + 1);
|
||||
self->lc->chars[self->lc->count++] = ch;
|
||||
cell->ch_or_idx = tc_get_or_insert_chars(self->text_cache, self->lc);
|
||||
cell->ch_is_idx = true;
|
||||
if (cell->is_multicell) {
|
||||
char_type ch_and_idx = cell->ch_and_idx;
|
||||
while (cell->x && x) cell = cpu_cells + --x;
|
||||
if (cell->y) return false; // not the top left cell
|
||||
text_in_cell(cell, self->text_cache, self->lc);
|
||||
MultiCellData mcd = {.val=self->lc->chars[self->lc->count]};
|
||||
ensure_space_for_chars(self->lc, self->lc->count + 2);
|
||||
self->lc->chars[self->lc->count++] = ch;
|
||||
self->lc->chars[self->lc->count++] = mcd.val;
|
||||
const char_type idx = tc_get_or_insert_chars(self->text_cache, self->lc);
|
||||
self->lc->count--;
|
||||
index_type x_limit = MIN(x + mcd_x_limit(mcd), self->columns);
|
||||
for (index_type v = y; v < y + mcd.scale; v++) {
|
||||
index_type x_limit = MIN(x + mcd_x_limit(cell), self->columns);
|
||||
for (index_type v = y; v < y + cell->scale; v++) {
|
||||
cpu_cells = linebuf_cpu_cells_for_line(self->linebuf, v);
|
||||
for (index_type h = x; h < x_limit; h++) cpu_cells[h].ch_or_idx = idx;
|
||||
for (index_type h = x; h < x_limit; h++) cpu_cells[h].ch_and_idx = ch_and_idx;
|
||||
linebuf_mark_line_dirty(self->linebuf, v);
|
||||
}
|
||||
} else {
|
||||
text_in_cell(cell, self->text_cache, self->lc);
|
||||
ensure_space_for_chars(self->lc, self->lc->count + 1);
|
||||
self->lc->chars[self->lc->count++] = ch;
|
||||
cell->ch_or_idx = tc_get_or_insert_chars(self->text_cache, self->lc);
|
||||
cell->ch_is_idx = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
@ -892,38 +875,23 @@ draw_combining_char(Screen *self, text_loop_state *s, char_type ch) {
|
|||
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 (self->lc->chars[base_pos + 1] == VS16 && (!cpu_cell->is_multicell || cell_multicell_data(cpu_cell, self->text_cache).width < 2) && is_emoji_presentation_base(self->lc->chars[base_pos])) {
|
||||
bool already_multicell = cpu_cell->is_multicell;
|
||||
if (already_multicell) {
|
||||
MultiCellData mcd = {.val=self->lc->chars[self->lc->count]};
|
||||
mcd.width *= 2;
|
||||
self->lc->chars[self->lc->count++] = mcd.val;
|
||||
} else {
|
||||
ensure_space_for_chars(self->lc, self->lc->count + 1);
|
||||
MultiCellData mcd = {.width=2, .scale=1, .msb=1};
|
||||
self->lc->chars[self->lc->count++] = mcd.val;
|
||||
cpu_cell->is_multicell = true;
|
||||
}
|
||||
cpu_cell->ch_or_idx = tc_get_or_insert_chars(self->text_cache, self->lc);
|
||||
if (!already_multicell) {
|
||||
if (xpos + 1 < self->columns) {
|
||||
CPUCell *second = cp + xpos + 1;
|
||||
if (second->is_multicell) nuke_multicell_char_at(self, xpos, ypos, false);
|
||||
zero_cells(s, second, gp + xpos + 1);
|
||||
self->cursor->x++;
|
||||
second->is_multicell = true; second->ch_or_idx = cpu_cell->ch_or_idx; second->ch_is_idx = true; second->x = 1;
|
||||
} else move_widened_char(self, s, cpu_cell, gpu_cell, xpos, ypos);
|
||||
}
|
||||
if (self->lc->chars[base_pos + 1] == VS16 && (!cpu_cell->is_multicell || cpu_cell->width < 2) && is_emoji_presentation_base(self->lc->chars[base_pos])) {
|
||||
cpu_cell->is_multicell = true;
|
||||
cpu_cell->width = 2;
|
||||
if (!cpu_cell->scale) cpu_cell->scale = 1;
|
||||
if (xpos + 1 < self->columns) {
|
||||
CPUCell *second = cp + xpos + 1;
|
||||
if (second->is_multicell) nuke_multicell_char_at(self, xpos, ypos, false);
|
||||
zero_cells(s, second, gp + xpos + 1);
|
||||
self->cursor->x++;
|
||||
*second = *cpu_cell; second->x = 1;
|
||||
} else move_widened_char(self, s, cpu_cell, gpu_cell, xpos, ypos);
|
||||
}
|
||||
} else if (ch == VS15) {
|
||||
const CPUCell *cpu_cell = cp + xpos;
|
||||
if (self->lc->chars[base_pos + 1] == VS15 && cpu_cell->is_multicell && is_emoji_presentation_base(self->lc->chars[base_pos])) {
|
||||
MultiCellData mcd = {.val=self->lc->chars[self->lc->count]};
|
||||
if (mcd.width == 2) {
|
||||
if (halve_multicell_width(self, xpos, ypos)) {
|
||||
self->cursor->x -= (mcd.scale * mcd.width / 2);
|
||||
}
|
||||
}
|
||||
if (self->lc->chars[base_pos + 1] == VS15 && cpu_cell->is_multicell && cpu_cell->width == 2 && is_emoji_presentation_base(self->lc->chars[base_pos])) {
|
||||
index_type deltax = (cpu_cell->scale * cpu_cell->width) / 2;
|
||||
if (halve_multicell_width(self, xpos, ypos)) self->cursor->x -= deltax;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1061,14 +1029,9 @@ draw_text_loop(Screen *self, const uint32_t *chars, size_t num_chars, text_loop_
|
|||
CPUCell *fc = s->cp + self->cursor->x;
|
||||
zero_cells(s, fc, s->gp + self->cursor->x);
|
||||
if (char_width == 2) {
|
||||
MultiCellData mcd = {.width = 2, .scale = 1, .msb = 1};
|
||||
RAII_ListOfChars(lc);
|
||||
lc.chars[lc.count++] = ch;
|
||||
lc.chars[lc.count++] = mcd.val;
|
||||
*fc = (CPUCell){.ch_or_idx=ch, .is_multicell=true, .width=2, .scale=1};
|
||||
CPUCell *second = fc + 1;
|
||||
if (second->is_multicell) nuke_multicell_char_at(self, self->cursor->x + 1, self->cursor->y, true);
|
||||
cell_set_chars(fc, self->text_cache, &lc);
|
||||
fc->x = 0; fc->y = 0; fc->is_multicell = true;
|
||||
*second = *fc; second->x = 1;
|
||||
s->gp[self->cursor->x + 1] = s->gp[self->cursor->x];
|
||||
self->cursor->x += 2;
|
||||
|
|
@ -1155,16 +1118,17 @@ screen_handle_multicell_command(Screen *self, const MultiCellCommand *cmd, const
|
|||
width = wcswidth_string(self->lc->chars);
|
||||
}
|
||||
if (!width) return;
|
||||
MultiCellData mcd = {
|
||||
.width=MIN(width, 15), .scale=MAX(1, MIN(cmd->scale, 15)), .subscale=MIN(cmd->subscale, 3),
|
||||
.explicitly_set=1, .msb=1, .vertical_align=MIN(cmd->vertical_align, 7u)
|
||||
CPUCell mcd = {
|
||||
.width=MIN(width, 15u), .scale=MAX(1, MIN(cmd->scale, 15u)), .subscale=MIN(cmd->subscale, 3u),
|
||||
.explicitly_set=1u, .vertical_align=MIN(cmd->vertical_align, 7u), .is_multicell=true
|
||||
};
|
||||
self->lc->chars[self->lc->count++] = mcd.val;
|
||||
width = mcd.width * mcd.scale;
|
||||
index_type height = mcd.scale;
|
||||
index_type max_height = self->margin_bottom - self->margin_top + 1;
|
||||
if (width > self->columns || height > max_height) return;
|
||||
PREPARE_FOR_DRAW_TEXT;
|
||||
mcd.hyperlink_id = s.cc.hyperlink_id;
|
||||
cell_set_chars(&mcd, self->text_cache, self->lc);
|
||||
if (self->columns < self->cursor->x + width) {
|
||||
if (self->modes.mDECAWM) {
|
||||
continue_to_next_line(self);
|
||||
|
|
@ -1187,16 +1151,13 @@ screen_handle_multicell_command(Screen *self, const MultiCellCommand *cmd, const
|
|||
if (self->modes.mIRM) insert_characters(self, self->cursor->x, width, y, true);
|
||||
}
|
||||
}
|
||||
CPUCell c = s.cc;
|
||||
c.ch_is_idx = true; c.ch_or_idx = tc_get_or_insert_chars(self->text_cache, self->lc);
|
||||
c.is_multicell = true;
|
||||
nuke_multicell_char_intersecting_with(self, self->cursor->x, self->cursor->x + width, self->cursor->y, self->cursor->y + height, true);
|
||||
for (index_type y = self->cursor->y; y < self->cursor->y + height; y++) {
|
||||
linebuf_init_cells(self->linebuf, y, &s.cp, &s.gp);
|
||||
linebuf_mark_line_dirty(self->linebuf, y);
|
||||
c.x = 0; c.y = y - self->cursor->y;
|
||||
for (index_type x = self->cursor->x; x < self->cursor->x + width; x++, c.x++) {
|
||||
s.cp[x] = c; s.gp[x] = s.g;
|
||||
mcd.x = 0; mcd.y = y - self->cursor->y;
|
||||
for (index_type x = self->cursor->x; x < self->cursor->x + width; x++, mcd.x++) {
|
||||
s.cp[x] = mcd; s.gp[x] = s.g;
|
||||
}
|
||||
}
|
||||
self->cursor->x += width;
|
||||
|
|
@ -2172,8 +2133,7 @@ screen_fake_move_cursor_to_position(Screen *self, index_type start_x, index_type
|
|||
}
|
||||
found_non_empty_cell = true;
|
||||
if (c->is_multicell) {
|
||||
MultiCellData mcd = cell_multicell_data(c, self->text_cache);
|
||||
x += mcd_x_limit(mcd);
|
||||
x += mcd_x_limit(c);
|
||||
} else x++;
|
||||
count += 1; // zsh requires a single arrow press to move past dualwidth chars
|
||||
}
|
||||
|
|
@ -2369,8 +2329,7 @@ screen_insert_lines(Screen *self, unsigned int count) {
|
|||
cells = linebuf_cpu_cells_for_line(self->linebuf, bottom);
|
||||
for (index_type x = 0; x < self->columns; x++) {
|
||||
if (cells[x].is_multicell) {
|
||||
MultiCellData mcd = cell_multicell_data(cells + x, self->text_cache);
|
||||
index_type y_limit = mcd.scale;
|
||||
index_type y_limit = cells[x].scale;
|
||||
if (cells[x].y + 1u < y_limit) {
|
||||
index_type orig = self->lines;
|
||||
self->lines = bottom + 1;
|
||||
|
|
@ -3753,8 +3712,7 @@ screen_draw_overlay_line(Screen *self) {
|
|||
// When the last character is a split multicell, make sure the next character is visible.
|
||||
CPUCell *c = self->linebuf->line->cpu_cells + len - 1;
|
||||
if (c->is_multicell) {
|
||||
MultiCellData mcd = cell_multicell_data(c, self->text_cache);
|
||||
if (c->x < mcd_x_limit(mcd) - 1) {
|
||||
if (c->x < mcd_x_limit(c) - 1) {
|
||||
do {
|
||||
c->is_multicell = false; c->ch_is_idx = false; c->ch_or_idx = ' ';
|
||||
if (!c->x) break;
|
||||
|
|
@ -4819,7 +4777,7 @@ current_char_width(Screen *self, PyObject *a UNUSED) {
|
|||
const CPUCell *c = linebuf_cpu_cells_for_line(self->linebuf, self->cursor->y) + self->cursor->x;
|
||||
if (c->is_multicell) {
|
||||
if (c->x || c->y) ans = 0;
|
||||
else ans = cell_multicell_data(c, self->text_cache).width;
|
||||
else ans = c->width;
|
||||
}
|
||||
}
|
||||
return PyLong_FromUnsignedLong(ans);
|
||||
|
|
@ -5173,16 +5131,15 @@ test_parse_written_data(Screen *screen, PyObject *args) {
|
|||
}
|
||||
|
||||
static PyObject*
|
||||
multicell_data_as_dict(MultiCellData mcd) {
|
||||
if (!mcd.msb) { PyErr_SetString(PyExc_RuntimeError, "mcd does not have its msb set"); return NULL; }
|
||||
multicell_data_as_dict(CPUCell mcd) {
|
||||
return Py_BuildValue("{sI sI sI sO sI}", "scale", (unsigned int)mcd.scale, "width", (unsigned int)mcd.width, "subscale", (unsigned int)mcd.subscale, "explicitly_set", mcd.explicitly_set ? Py_True : Py_False, "vertical_align", mcd.vertical_align);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
cpu_cell_as_dict(CPUCell *c, TextCache *tc, ListOfChars *lc, HYPERLINK_POOL_HANDLE h) {
|
||||
text_in_cell(c, tc, lc);
|
||||
RAII_PyObject(mcd, lc->is_multicell ? multicell_data_as_dict((MultiCellData){.val=lc->chars[lc->count]}) : Py_NewRef(Py_None));
|
||||
if ((lc->is_multicell && !lc->is_topleft) || (lc->count == 1 && lc->chars[0] == 0)) lc->count = 0;
|
||||
RAII_PyObject(mcd, c->is_multicell ? multicell_data_as_dict(*c) : Py_NewRef(Py_None));
|
||||
if ((c->is_multicell && (c->x + c->y)) || (lc->count == 1 && lc->chars[0] == 0)) lc->count = 0;
|
||||
RAII_PyObject(text, PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, lc->chars, lc->count));
|
||||
const char *url = c->hyperlink_id ? get_hyperlink_for_id(h, c->hyperlink_id, false) : NULL;
|
||||
RAII_PyObject(hyperlink, url ? PyUnicode_FromString(url) : Py_NewRef(Py_None));
|
||||
|
|
|
|||
|
|
@ -378,8 +378,7 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, c
|
|||
line_for_cursor && (cursor_cell = line_for_cursor->cpu_cells + cursor->x)->is_multicell &&
|
||||
cursor_cell->x == 0
|
||||
) {
|
||||
MultiCellData mcd = cell_multicell_data(cursor_cell, screen->text_cache);
|
||||
rd->cursor_w = mcd.width * mcd.scale;
|
||||
rd->cursor_w = mcd_x_limit(cursor_cell);
|
||||
}
|
||||
|
||||
rd->xnum = screen->columns; rd->ynum = screen->lines;
|
||||
|
|
|
|||
|
|
@ -116,7 +116,8 @@ 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);
|
||||
// we ensure space for one extra byte for ANSI escape code trailer if multicell
|
||||
ensure_space_for(output, buf, output->buf[0], output->len + count + 1, capacity, 2048, false);
|
||||
memcpy(output->buf + output->len, self->array.items[idx].chars, sizeof(output->buf[0]) * count);
|
||||
output->len += count;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@
|
|||
typedef struct ListOfChars {
|
||||
char_type *chars;
|
||||
size_t count, capacity;
|
||||
bool is_multicell, is_topleft;
|
||||
} ListOfChars;
|
||||
|
||||
#define LIST_OF_CHARS_STACK_SIZE 4
|
||||
|
|
|
|||
|
|
@ -302,7 +302,7 @@ def big_a(x, y):
|
|||
assert_line('ab_c\0\0', -2)
|
||||
assert_line('\0__\0\0\0', -1)
|
||||
self.ae(s.historybuf.line(1).as_ansi(), f'a\x1b]{TEXT_SIZE_CODE};s=2;b\x07c')
|
||||
self.ae(s.historybuf.line(0).as_ansi(), ' ')
|
||||
self.ae(s.historybuf.line(0).as_ansi(), '')
|
||||
|
||||
# Insert lines
|
||||
s.reset()
|
||||
|
|
|
|||
Loading…
Reference in a new issue