mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-10-24 06:04:08 +00:00
Allow non-width multicell code to fill multiple cells using native width algo
This commit is contained in:
parent
844d538e40
commit
b1c80d212f
10 changed files with 302 additions and 163 deletions
|
|
@ -140,7 +140,7 @@ def generate(
|
|||
case PAYLOAD: {
|
||||
sz = parser_buf_pos - pos;
|
||||
payload_start = pos;
|
||||
g.payload_sz = MAX(BUF_EXTRA, sz);
|
||||
g.payload_sz = sz;
|
||||
pos = parser_buf_pos;
|
||||
} break;
|
||||
'''
|
||||
|
|
|
|||
|
|
@ -264,10 +264,10 @@ static void
|
|||
pagerhist_push(HistoryBuf *self, ANSIBuf *as_ansi_buf) {
|
||||
PagerHistoryBuf *ph = self->pagerhist;
|
||||
if (!ph) return;
|
||||
const GPUCell *prev_cell = NULL;
|
||||
Line l = {.xnum=self->xnum, .text_cache=self->text_cache};
|
||||
init_line(self, self->start_of_data, &l);
|
||||
line_as_ansi(&l, as_ansi_buf, &prev_cell, 0, l.xnum, 0);
|
||||
ANSILineState s = {.output_buf=as_ansi_buf};
|
||||
line_as_ansi(&l, &s, 0, l.xnum, 0);
|
||||
pagerhist_write_bytes(ph, (const uint8_t*)"\x1b[m", 3);
|
||||
if (pagerhist_write_ucs4(ph, as_ansi_buf->buf, as_ansi_buf->len)) {
|
||||
char line_end[2]; size_t num = 0;
|
||||
|
|
@ -353,11 +353,10 @@ static PyObject*
|
|||
as_ansi(HistoryBuf *self, PyObject *callback) {
|
||||
#define as_ansi_doc "as_ansi(callback) -> The contents of this buffer as ANSI escaped text. callback is called with each successive line."
|
||||
Line l = {.xnum=self->xnum, .text_cache=self->text_cache};
|
||||
const GPUCell *prev_cell = NULL;
|
||||
ANSIBuf output = {0};
|
||||
ANSIBuf output = {0}; ANSILineState s = {.output_buf=&output};
|
||||
for(unsigned int i = 0; i < self->count; i++) {
|
||||
init_line(self, i, &l);
|
||||
line_as_ansi(&l, &output, &prev_cell, 0, l.xnum, 0);
|
||||
line_as_ansi(&l, &s, 0, l.xnum, 0);
|
||||
if (!l.cpu_cells[l.xnum - 1].next_char_was_wrapped) {
|
||||
ensure_space_for(&output, buf, Py_UCS4, output.len + 1, capacity, 2048, false);
|
||||
output.buf[output.len++] = '\n';
|
||||
|
|
|
|||
|
|
@ -455,11 +455,10 @@ as_ansi(LineBuf *self, PyObject *callback) {
|
|||
Line l = {.xnum=self->xnum, .text_cache=self->text_cache};
|
||||
// remove trailing empty lines
|
||||
index_type ylimit = self->ynum - 1;
|
||||
const GPUCell *prev_cell = NULL;
|
||||
ANSIBuf output = {0};
|
||||
ANSIBuf output = {0}; ANSILineState s = {.output_buf=&output};
|
||||
do {
|
||||
init_line(self, &l, self->line_map[ylimit]);
|
||||
line_as_ansi(&l, &output, &prev_cell, 0, l.xnum, 0);
|
||||
line_as_ansi(&l, &s, 0, l.xnum, 0);
|
||||
if (output.len) break;
|
||||
ylimit--;
|
||||
} while(ylimit > 0);
|
||||
|
|
@ -467,7 +466,7 @@ as_ansi(LineBuf *self, PyObject *callback) {
|
|||
for(index_type i = 0; i <= ylimit; i++) {
|
||||
bool output_newline = !linebuf_line_ends_with_continuation(self, i);
|
||||
init_line(self, &l, self->line_map[i]);
|
||||
line_as_ansi(&l, &output, &prev_cell, 0, l.xnum, 0);
|
||||
line_as_ansi(&l, &s, 0, l.xnum, 0);
|
||||
if (output_newline) {
|
||||
ensure_space_for(&output, buf, Py_UCS4, output.len + 1, capacity, 2048, false);
|
||||
output.buf[output.len++] = 10; // 10 = \n
|
||||
|
|
|
|||
234
kitty/line.c
234
kitty/line.c
|
|
@ -53,50 +53,90 @@ nonnegative_integer_as_utf32(unsigned num, ANSIBuf *output) {
|
|||
return num_digits;
|
||||
}
|
||||
|
||||
static unsigned
|
||||
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->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 (output->buf[output->len - 1] == ':') output->len--;
|
||||
w(';');
|
||||
#undef w
|
||||
return output->len - pos;
|
||||
static void
|
||||
ensure_space_in_ansi_output_buf(ANSILineState *s, size_t extra) {
|
||||
ensure_space_for(s->output_buf, buf, s->output_buf->buf[0], s->output_buf->len + extra, capacity, 2048, false);
|
||||
}
|
||||
|
||||
static unsigned
|
||||
text_in_cell_ansi(const CPUCell *c, TextCache *tc, ANSIBuf *output) {
|
||||
unsigned n = 0;
|
||||
if (c->is_multicell) {
|
||||
if (c->x || c->y) return 0;
|
||||
n = write_multicell_ansi_prefix(c, output);
|
||||
write_multicell_ansi_prefix(ANSILineState *s, const CPUCell *mcd) {
|
||||
ensure_space_in_ansi_output_buf(s, 128);
|
||||
s->current_multicell_state = mcd;
|
||||
s->escape_code_written = true;
|
||||
unsigned pos = s->output_buf->len;
|
||||
#define w(x) s->output_buf->buf[s->output_buf->len++] = x
|
||||
w(0x1b); w(']');
|
||||
for (unsigned i = 0; i < sizeof(xstr(TEXT_SIZE_CODE)) - 1; i++) w(xstr(TEXT_SIZE_CODE)[i]);
|
||||
w(';');
|
||||
w('w'); w('='); nonnegative_integer_as_utf32(mcd->width, s->output_buf); w(':');
|
||||
if (mcd->scale > 1) {
|
||||
w('s'); w('='); nonnegative_integer_as_utf32(mcd->scale, s->output_buf); w(':');
|
||||
}
|
||||
if (mcd->subscale) {
|
||||
w('S'); w('='); nonnegative_integer_as_utf32(mcd->subscale, s->output_buf); w(':');
|
||||
}
|
||||
if (s->output_buf->buf[s->output_buf->len - 1] == ':') s->output_buf->len--;
|
||||
w(';');
|
||||
#undef w
|
||||
return s->output_buf->len - pos;
|
||||
}
|
||||
|
||||
static void
|
||||
close_multicell(ANSILineState *s) {
|
||||
if (s->current_multicell_state) {
|
||||
ensure_space_in_ansi_output_buf(s, 1);
|
||||
s->output_buf->buf[s->output_buf->len++] = '\a';
|
||||
s->current_multicell_state = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
start_multicell_if_needed(ANSILineState *s, const CPUCell *c) {
|
||||
if (!c->natural_width || c->scale > 1 || c->subscale || c->vertical_align) write_multicell_ansi_prefix(s, c);
|
||||
}
|
||||
|
||||
static bool
|
||||
multicell_is_continuation_of_previous(const CPUCell *prev, const CPUCell *curr) {
|
||||
if (prev->scale != curr->scale || prev->subscale != curr->subscale || prev->vertical_align != curr->vertical_align) return false;
|
||||
if (prev->natural_width) return curr->natural_width;
|
||||
return prev->width == curr->width && !curr->natural_width;
|
||||
}
|
||||
|
||||
static void
|
||||
text_in_cell_ansi(ANSILineState *s, const CPUCell *c, TextCache *tc) {
|
||||
if (c->is_multicell) {
|
||||
if (c->x || c->y) return;
|
||||
if (s->current_multicell_state) {
|
||||
if (!multicell_is_continuation_of_previous(s->current_multicell_state, c)) {
|
||||
close_multicell(s);
|
||||
start_multicell_if_needed(s, c);
|
||||
}
|
||||
} else start_multicell_if_needed(s, c);
|
||||
} else close_multicell(s);
|
||||
|
||||
size_t pos = s->output_buf->len;
|
||||
if (c->ch_is_idx) {
|
||||
n += tc_chars_at_index_ansi(tc, c->ch_or_idx, output);
|
||||
tc_chars_at_index_ansi(tc, c->ch_or_idx, s->output_buf);
|
||||
} 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;
|
||||
ensure_space_in_ansi_output_buf(s, 2);
|
||||
s->output_buf->buf[s->output_buf->len++] = c->ch_or_idx;
|
||||
}
|
||||
if (s->output_buf->len > pos) {
|
||||
switch (s->output_buf->buf[pos]) {
|
||||
case 0: s->output_buf->buf[pos] = ' '; break;
|
||||
case '\t': {
|
||||
unsigned num_cells_to_skip_for_tab = 0, n = s->output_buf->len - pos;
|
||||
if (n - pos > 1) {
|
||||
num_cells_to_skip_for_tab = s->output_buf->buf[s->output_buf->len - n + 1];
|
||||
s->output_buf->len -= n - 1;
|
||||
}
|
||||
const CPUCell *next = c + 1;
|
||||
while (num_cells_to_skip_for_tab && pos + 1 < s->limit && cell_is_char(next, ' ')) {
|
||||
num_cells_to_skip_for_tab--; pos++; next++;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
if (c->is_multicell) {
|
||||
output->buf[output->len++] = '\a';
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -407,85 +447,87 @@ write_mark(const char *mark, ANSIBuf *output) {
|
|||
|
||||
}
|
||||
|
||||
static void
|
||||
write_sgr_to_ansi_buf(ANSILineState *s, const char *val) {
|
||||
close_multicell(s);
|
||||
ensure_space_in_ansi_output_buf(s, 128);
|
||||
s->escape_code_written = true;
|
||||
write_sgr(val, s->output_buf);
|
||||
}
|
||||
|
||||
static void
|
||||
write_ch_to_ansi_buf(ANSILineState *s, char_type ch) {
|
||||
close_multicell(s);
|
||||
ensure_space_in_ansi_output_buf(s, 1);
|
||||
s->output_buf->buf[s->output_buf->len++] = ch;
|
||||
}
|
||||
|
||||
static void
|
||||
write_hyperlink_to_ansi_buf(ANSILineState *s, hyperlink_id_type hid) {
|
||||
close_multicell(s);
|
||||
ensure_space_in_ansi_output_buf(s, 2256);
|
||||
s->escape_code_written = true;
|
||||
write_hyperlink(hid, s->output_buf);
|
||||
}
|
||||
|
||||
static void
|
||||
write_mark_to_ansi_buf(ANSILineState *s, const char *m) {
|
||||
close_multicell(s);
|
||||
ensure_space_in_ansi_output_buf(s, 64);
|
||||
s->escape_code_written = true;
|
||||
write_mark(m, s->output_buf);
|
||||
}
|
||||
|
||||
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, 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); }
|
||||
#define WRITE_MARK(val) { ENSURE_SPACE(64); escape_code_written = true; write_mark(val, output); }
|
||||
bool escape_code_written = false;
|
||||
output->len = 0;
|
||||
index_type limit = MIN(stop_before, xlimit_for_line(self));
|
||||
if (prefix_char) { WRITE_CH(prefix_char); }
|
||||
line_as_ansi(Line *self, ANSILineState *s, index_type start_at, index_type stop_before, char_type prefix_char) {
|
||||
s->output_buf->len = 0;
|
||||
s->limit = MIN(stop_before, xlimit_for_line(self));
|
||||
s->current_multicell_state = NULL;
|
||||
s->escape_code_written = false;
|
||||
if (prefix_char) write_ch_to_ansi_buf(s, prefix_char);
|
||||
|
||||
switch (self->attrs.prompt_kind) {
|
||||
case UNKNOWN_PROMPT_KIND:
|
||||
break;
|
||||
case PROMPT_START:
|
||||
WRITE_MARK("A");
|
||||
break;
|
||||
case SECONDARY_PROMPT:
|
||||
WRITE_MARK("A;k=s");
|
||||
break;
|
||||
case OUTPUT_START:
|
||||
WRITE_MARK("C");
|
||||
break;
|
||||
case PROMPT_START: write_mark_to_ansi_buf(s, "A"); break;
|
||||
case SECONDARY_PROMPT: write_mark_to_ansi_buf(s, "A;k=s"); break;
|
||||
case OUTPUT_START: write_mark_to_ansi_buf(s, "C"); break;
|
||||
}
|
||||
if (limit <= start_at) return escape_code_written;
|
||||
if (s->limit <= start_at) return s->escape_code_written;
|
||||
|
||||
static const GPUCell blank_cell = { 0 };
|
||||
GPUCell *cell;
|
||||
if (*prev_cell == NULL) *prev_cell = &blank_cell;
|
||||
if (s->prev_gpu_cell == NULL) s->prev_gpu_cell = &blank_cell;
|
||||
const CellAttrs mask_for_sgr = {.val=SGR_MASK};
|
||||
|
||||
#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
|
||||
#define CMP_ATTRS (cell->attrs.val & mask_for_sgr.val) != (s->prev_gpu_cell->attrs.val & mask_for_sgr.val)
|
||||
#define CMP(x) (cell->x != s->prev_gpu_cell->x)
|
||||
|
||||
for (index_type pos=start_at; pos < limit; pos++) {
|
||||
if (output->hyperlink_pool) {
|
||||
hyperlink_id_type hid = self->cpu_cells[pos].hyperlink_id;
|
||||
if (hid != output->active_hyperlink_id) {
|
||||
WRITE_HYPERLINK(hid);
|
||||
}
|
||||
for (s->pos=start_at; s->pos < s->limit; s->pos++) {
|
||||
if (s->output_buf->hyperlink_pool) {
|
||||
hyperlink_id_type hid = self->cpu_cells[s->pos].hyperlink_id;
|
||||
if (hid != s->output_buf->active_hyperlink_id) write_hyperlink_to_ansi_buf(s, hid);
|
||||
}
|
||||
cell = &self->gpu_cells[pos];
|
||||
cell = &self->gpu_cells[s->pos];
|
||||
if (CMP_ATTRS || CMP(fg) || CMP(bg) || CMP(decoration_fg)) {
|
||||
const char *sgr = cell_as_sgr(cell, *prev_cell);
|
||||
if (*sgr) WRITE_SGR(sgr);
|
||||
const char *sgr = cell_as_sgr(cell, s->prev_gpu_cell);
|
||||
if (*sgr) write_sgr_to_ansi_buf(s, sgr);
|
||||
}
|
||||
|
||||
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] == '\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;
|
||||
}
|
||||
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++;
|
||||
}
|
||||
}
|
||||
*prev_cell = cell;
|
||||
text_in_cell_ansi(s, self->cpu_cells + s->pos, self->text_cache);
|
||||
s->prev_gpu_cell = cell;
|
||||
}
|
||||
return escape_code_written;
|
||||
close_multicell(s);
|
||||
return s->escape_code_written;
|
||||
#undef CMP_ATTRS
|
||||
#undef CMP
|
||||
#undef WRITE_SGR
|
||||
#undef WRITE_CH
|
||||
#undef ENSURE_SPACE
|
||||
#undef WRITE_HYPERLINK
|
||||
#undef WRITE_MARK
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
as_ansi(Line* self, PyObject *a UNUSED) {
|
||||
#define as_ansi_doc "Return the line's contents with ANSI (SGR) escape codes for formatting"
|
||||
const GPUCell *prev_cell = NULL;
|
||||
ANSIBuf output = {0};
|
||||
line_as_ansi(self, &output, &prev_cell, 0, self->xnum, 0);
|
||||
ANSIBuf output = {0}; ANSILineState s = {.output_buf=&output};
|
||||
line_as_ansi(self, &s, 0, self->xnum, 0);
|
||||
PyObject *ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, output.buf, output.len);
|
||||
free(output.buf);
|
||||
return ans;
|
||||
|
|
@ -910,7 +952,7 @@ as_text_generic(PyObject *args, void *container, get_line_func get_line, index_t
|
|||
RAII_PyObject(cr, PyUnicode_FromString("\r"));
|
||||
RAII_PyObject(sgr_reset, PyUnicode_FromString("\x1b[m"));
|
||||
if (nl == NULL || cr == NULL || sgr_reset == NULL) return NULL;
|
||||
const GPUCell *prev_cell = NULL;
|
||||
ANSILineState s = {.output_buf=ansibuf};
|
||||
ansibuf->active_hyperlink_id = 0;
|
||||
bool need_newline = false;
|
||||
for (index_type y = 0; y < lines; y++) {
|
||||
|
|
@ -920,11 +962,11 @@ as_text_generic(PyObject *args, void *container, get_line_func get_line, index_t
|
|||
if (as_ansi) {
|
||||
// less has a bug where it resets colors when it sees a \r, so work
|
||||
// around it by resetting SGR at the start of every line. This is
|
||||
// pretty sad performance wise, but I guess it will remain till I
|
||||
// get around to writing a nice pager kitten.
|
||||
// pretty sad performance wise, but I guess it will remain as it
|
||||
// makes writing pagers easier.
|
||||
// see https://github.com/kovidgoyal/kitty/issues/2381
|
||||
prev_cell = NULL;
|
||||
line_as_ansi(line, ansibuf, &prev_cell, 0, line->xnum, 0);
|
||||
s.prev_gpu_cell = NULL;
|
||||
line_as_ansi(line, &s, 0, line->xnum, 0);
|
||||
t = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, ansibuf->buf, ansibuf->len);
|
||||
if (t && ansibuf->len > 0) APPEND(sgr_reset);
|
||||
} else {
|
||||
|
|
|
|||
11
kitty/line.h
11
kitty/line.h
|
|
@ -61,7 +61,7 @@ typedef union CPUCell {
|
|||
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 natural_width: 1;
|
||||
char_type x : 8;
|
||||
char_type y : 4;
|
||||
char_type subscale: 2;
|
||||
|
|
@ -107,6 +107,15 @@ typedef struct MultiCellCommand {
|
|||
size_t payload_sz;
|
||||
} MultiCellCommand;
|
||||
|
||||
typedef struct ANSILineOutput {
|
||||
const GPUCell *prev_gpu_cell;
|
||||
const CPUCell *current_multicell_state;
|
||||
index_type pos, limit;
|
||||
ANSIBuf *output_buf;
|
||||
bool escape_code_written;
|
||||
} ANSILineState;
|
||||
|
||||
|
||||
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 *);
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ char_type line_get_char(Line *self, index_type at);
|
|||
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));
|
||||
bool line_as_ansi(Line *self, ANSILineState *s, 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_for_fallback(const ListOfChars *lc, Py_UCS4 *buf);
|
||||
size_t cell_as_utf8_for_fallback(const ListOfChars *lc, char *buf);
|
||||
|
|
|
|||
2
kitty/parse-multicell-command.h
generated
2
kitty/parse-multicell-command.h
generated
|
|
@ -148,7 +148,7 @@ static inline void parse_multicell_code(PS *self, uint8_t *parser_buf,
|
|||
case PAYLOAD: {
|
||||
sz = parser_buf_pos - pos;
|
||||
payload_start = pos;
|
||||
g.payload_sz = MAX(BUF_EXTRA, sz);
|
||||
g.payload_sz = sz;
|
||||
pos = parser_buf_pos;
|
||||
} break;
|
||||
|
||||
|
|
|
|||
101
kitty/screen.c
101
kitty/screen.c
|
|
@ -902,6 +902,7 @@ draw_combining_char(Screen *self, text_loop_state *s, char_type ch) {
|
|||
if (self->lc->chars[base_pos + 1] == VS16 && !cpu_cell->is_multicell && is_emoji_presentation_base(self->lc->chars[base_pos])) {
|
||||
cpu_cell->is_multicell = true;
|
||||
cpu_cell->width = 2;
|
||||
cpu_cell->natural_width = true;
|
||||
if (!cpu_cell->scale) cpu_cell->scale = 1;
|
||||
if (xpos + 1 < self->columns) {
|
||||
CPUCell *second = cp + xpos + 1;
|
||||
|
|
@ -1075,7 +1076,7 @@ draw_text_loop(Screen *self, const uint32_t *chars, size_t num_chars, text_loop_
|
|||
} else nuke_multicell_char_at(self, self->cursor->x + 1, self->cursor->y, true);
|
||||
}
|
||||
zero_cells(s, fc, s->gp + self->cursor->x);
|
||||
*fc = (CPUCell){.ch_or_idx=ch, .is_multicell=true, .width=2, .scale=1};
|
||||
*fc = (CPUCell){.ch_or_idx=ch, .is_multicell=true, .width=2, .scale=1, .natural_width=true};
|
||||
*second = *fc; second->x = 1;
|
||||
s->gp[self->cursor->x + 1] = s->gp[self->cursor->x];
|
||||
self->cursor->x += 2;
|
||||
|
|
@ -1133,12 +1134,12 @@ decode_utf8_safe_string(const uint8_t *src, size_t sz, uint32_t *dest) {
|
|||
// dest must be an array of size at least sz
|
||||
uint32_t codep = 0;
|
||||
UTF8State state = 0, prev = UTF8_ACCEPT;
|
||||
size_t i, d;
|
||||
for (i = 0, d = 0; i < sz; i++) {
|
||||
size_t i = 0, d = 0;
|
||||
for (; i < sz; i++) {
|
||||
switch(decode_utf8(&state, &codep, src[i])) {
|
||||
case UTF8_ACCEPT:
|
||||
// Ignore C0 and C1 chars
|
||||
if (codep >= ' ' && !(codep >= DEL && codep <= 159)) dest[d++] = codep;
|
||||
if (codep >= ' ' && !(DEL <= codep && codep <= 159)) dest[d++] = codep;
|
||||
break;
|
||||
case UTF8_REJECT:
|
||||
state = UTF8_ACCEPT;
|
||||
|
|
@ -1150,39 +1151,16 @@ decode_utf8_safe_string(const uint8_t *src, size_t sz, uint32_t *dest) {
|
|||
return d;
|
||||
}
|
||||
|
||||
void
|
||||
screen_handle_multicell_command(Screen *self, const MultiCellCommand *cmd, const uint8_t *payload) {
|
||||
screen_on_input(self);
|
||||
if (!cmd->payload_sz) return;
|
||||
ensure_space_for_chars(self->lc, cmd->payload_sz + 1);
|
||||
self->lc->count = decode_utf8_safe_string(payload, cmd->payload_sz, self->lc->chars);
|
||||
if (!self->lc->count) return;
|
||||
index_type width = cmd->width;
|
||||
if (!width) {
|
||||
self->lc->chars[self->lc->count] = 0;
|
||||
width = wcswidth_string(self->lc->chars);
|
||||
}
|
||||
if (!width) return;
|
||||
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
|
||||
};
|
||||
width = mcd.width * mcd.scale;
|
||||
static void
|
||||
handle_fixed_width_multicell_command(Screen *self, CPUCell mcd, const ListOfChars *lc) {
|
||||
index_type 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);
|
||||
} else {
|
||||
self->cursor->x = self->columns - width;
|
||||
CPUCell *cp = linebuf_cpu_cell_at(self->linebuf, self->cursor->x, self->cursor->y);
|
||||
if (cp->is_multicell) replace_multicell_char_under_cursor_with_spaces(self);
|
||||
}
|
||||
}
|
||||
cell_set_chars(&mcd, self->text_cache, lc);
|
||||
move_cursor_past_multicell(self, width);
|
||||
if (height > 1) {
|
||||
index_type available_height = self->margin_bottom - self->cursor->y + 1;
|
||||
if (height > available_height) {
|
||||
|
|
@ -1196,12 +1174,12 @@ screen_handle_multicell_command(Screen *self, const MultiCellCommand *cmd, const
|
|||
if (self->modes.mIRM) insert_characters(self, self->cursor->x, width, y, 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);
|
||||
mcd.x = 0; mcd.y = y - self->cursor->y;
|
||||
for (index_type x = self->cursor->x; x < self->cursor->x + width; x++, mcd.x++) {
|
||||
if (s.cp[x].is_multicell) nuke_multicell_char_at(self, x, y, s.cp[x].x + s.cp[x].y > 0);
|
||||
s.cp[x] = mcd; s.gp[x] = s.g;
|
||||
}
|
||||
}
|
||||
|
|
@ -1209,6 +1187,57 @@ screen_handle_multicell_command(Screen *self, const MultiCellCommand *cmd, const
|
|||
self->is_dirty = true;
|
||||
}
|
||||
|
||||
static void
|
||||
handle_variable_width_multicell_command(Screen *self, CPUCell mcd, ListOfChars *lc) {
|
||||
ensure_space_for_chars(lc, lc->count + 1); lc->chars[lc->count] = 0;
|
||||
mcd.width = wcswidth_string(lc->chars);
|
||||
if (!mcd.width) { lc->count = 0; return; }
|
||||
handle_fixed_width_multicell_command(self, mcd, lc);
|
||||
lc->count = 0;
|
||||
}
|
||||
|
||||
void
|
||||
screen_handle_multicell_command(Screen *self, const MultiCellCommand *cmd, const uint8_t *payload) {
|
||||
screen_on_input(self);
|
||||
if (!cmd->payload_sz) return;
|
||||
ensure_space_for_chars(self->lc, cmd->payload_sz + 1);
|
||||
self->lc->count = decode_utf8_safe_string(payload, cmd->payload_sz, self->lc->chars);
|
||||
if (!self->lc->count) return;
|
||||
CPUCell mcd = {
|
||||
.width=MIN(cmd->width, 15u), .scale=MAX(1, MIN(cmd->scale, 15u)), .subscale=MIN(cmd->subscale, 3u),
|
||||
.vertical_align=MIN(cmd->vertical_align, 7u), .is_multicell=true
|
||||
};
|
||||
if (mcd.width) handle_fixed_width_multicell_command(self, mcd, self->lc);
|
||||
else {
|
||||
RAII_ListOfChars(lc);
|
||||
mcd.natural_width = true;
|
||||
for (unsigned i = 0; i < self->lc->count; i++) {
|
||||
char_type ch = self->lc->chars[i];
|
||||
if (is_ignored_char(ch)) continue;
|
||||
if (is_combining_char(ch)) {
|
||||
if (is_flag_codepoint(ch)) {
|
||||
if (lc.count == 1) {
|
||||
if (is_flag_pair(lc.chars[0], ch)) {
|
||||
lc.chars[lc.count++] = ch; handle_variable_width_multicell_command(self, mcd, &lc);
|
||||
} else {
|
||||
handle_variable_width_multicell_command(self, mcd, &lc); lc.chars[lc.count++] = ch;
|
||||
}
|
||||
} else {
|
||||
handle_variable_width_multicell_command(self, mcd, &lc); lc.chars[lc.count++] = ch;
|
||||
}
|
||||
} else {
|
||||
if (!lc.count) continue;
|
||||
lc.chars[lc.count++] = ch;
|
||||
}
|
||||
} else {
|
||||
if (lc.count) handle_variable_width_multicell_command(self, mcd, &lc);
|
||||
lc.chars[lc.count++] = ch;
|
||||
}
|
||||
}
|
||||
if (lc.count) handle_variable_width_multicell_command(self, mcd, &lc);
|
||||
}
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
// Graphics {{{
|
||||
|
|
@ -3437,9 +3466,9 @@ ansi_for_range(Screen *self, const Selection *sel, bool insert_newlines, bool st
|
|||
RAII_PyObject(nl, PyUnicode_FromString("\n"));
|
||||
if (!ans || !nl) return NULL;
|
||||
ANSIBuf output = {0};
|
||||
const GPUCell *prev_cell = NULL;
|
||||
bool has_escape_codes = false;
|
||||
bool need_newline = false;
|
||||
ANSILineState s = {.output_buf=&output};
|
||||
for (int i = 0, y = idata.y; y < limit; y++, i++) {
|
||||
Line *line = range_line_(self, y);
|
||||
XRange xr = xrange_for_iteration(&idata, y, line);
|
||||
|
|
@ -3456,7 +3485,7 @@ ansi_for_range(Screen *self, const Selection *sel, bool insert_newlines, bool st
|
|||
}
|
||||
}
|
||||
}
|
||||
if (line_as_ansi(line, &output, &prev_cell, xr.x, x_limit, prefix_char)) has_escape_codes = true;
|
||||
if (line_as_ansi(line, &s, xr.x, x_limit, prefix_char)) has_escape_codes = true;
|
||||
need_newline = insert_newlines && !line->cpu_cells[line->xnum-1].next_char_was_wrapped;
|
||||
PyObject *t = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, output.buf, output.len);
|
||||
if (!t) return NULL;
|
||||
|
|
@ -5177,7 +5206,7 @@ test_parse_written_data(Screen *screen, PyObject *args) {
|
|||
|
||||
static PyObject*
|
||||
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);
|
||||
return Py_BuildValue("{sI sI sI sO sI}", "scale", (unsigned int)mcd.scale, "width", (unsigned int)mcd.width, "subscale", (unsigned int)mcd.subscale, "natural_width", mcd.natural_width ? Py_True : Py_False, "vertical_align", mcd.vertical_align);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
|
|
|
|||
|
|
@ -540,7 +540,7 @@ dispatch_osc(PS *self, uint8_t *buf, size_t limit, bool is_extended_osc) {
|
|||
DISPATCH_OSC_WITH_CODE(clipboard_control);
|
||||
END_DISPATCH
|
||||
case 66:
|
||||
parse_multicell_code(self, buf + i, limit - 1);
|
||||
parse_multicell_code(self, buf + i, limit - i);
|
||||
break;
|
||||
case 133:
|
||||
#ifdef DUMP_COMMANDS
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
# License: GPLv3 Copyright: 2024, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
from kitty.fast_data_types import TEXT_SIZE_CODE
|
||||
from kitty.fast_data_types import TEXT_SIZE_CODE, wcswidth
|
||||
|
||||
from . import BaseTest
|
||||
from . import BaseTest, parse_bytes
|
||||
from . import draw_multicell as multicell
|
||||
|
||||
|
||||
|
|
@ -15,6 +15,11 @@ class TestMulticell(BaseTest):
|
|||
|
||||
|
||||
def test_multicell(self: TestMulticell) -> None:
|
||||
from kitty.tab_bar import as_rgb
|
||||
from kitty.window import as_text
|
||||
|
||||
def as_ansi():
|
||||
return as_text(s, as_ansi=True)
|
||||
|
||||
def ac(x_, y_, **assertions): # assert cell
|
||||
cell = s.cpu_cells(y_, x_)
|
||||
|
|
@ -39,7 +44,7 @@ def test_multicell(self: TestMulticell) -> None:
|
|||
ae('subscale')
|
||||
ae('vertical_align')
|
||||
ae('text')
|
||||
ae('explicitly_set')
|
||||
ae('natural_width')
|
||||
|
||||
if 'cursor' in assertions:
|
||||
self.ae(assertions['cursor'], (s.cursor.x, s.cursor.y), msg)
|
||||
|
|
@ -85,21 +90,21 @@ def test_multicell(self: TestMulticell) -> None:
|
|||
s.reset()
|
||||
ac(0, 0, is_multicell=False)
|
||||
multicell(s, 'a')
|
||||
ac(0, 0, is_multicell=True, width=1, scale=1, subscale=0, x=0, y=0, text='a', explicitly_set=True, cursor=(1, 0))
|
||||
ac(0, 0, is_multicell=True, width=1, scale=1, subscale=0, x=0, y=0, text='a', natural_width=True, cursor=(1, 0))
|
||||
ac(0, 1, is_multicell=False), ac(1, 0, is_multicell=False), ac(1, 1, is_multicell=False)
|
||||
s.draw('莊')
|
||||
ac(0, 0, is_multicell=True, width=1, scale=1, subscale=0, x=0, y=0, text='a', explicitly_set=True)
|
||||
ac(1, 0, is_multicell=True, width=2, scale=1, subscale=0, x=0, y=0, text='莊', explicitly_set=False, cursor=(3, 0))
|
||||
ac(2, 0, is_multicell=True, width=2, scale=1, subscale=0, x=1, y=0, text='', explicitly_set=False)
|
||||
ac(0, 0, is_multicell=True, width=1, scale=1, subscale=0, x=0, y=0, text='a', natural_width=True)
|
||||
ac(1, 0, is_multicell=True, width=2, scale=1, subscale=0, x=0, y=0, text='莊', natural_width=True, cursor=(3, 0))
|
||||
ac(2, 0, is_multicell=True, width=2, scale=1, subscale=0, x=1, y=0, text='', natural_width=True)
|
||||
for x in range(s.columns):
|
||||
ac(x, 1, is_multicell=False)
|
||||
s.cursor.x = 0
|
||||
multicell(s, 'a', width=2, scale=2, subscale=3)
|
||||
ac(0, 0, is_multicell=True, width=2, scale=2, subscale=3, x=0, y=0, text='a', explicitly_set=True, cursor=(4, 0))
|
||||
ac(0, 0, is_multicell=True, width=2, scale=2, subscale=3, x=0, y=0, text='a', natural_width=False, cursor=(4, 0))
|
||||
for x in range(1, 4):
|
||||
ac(x, 0, is_multicell=True, width=2, scale=2, subscale=3, x=x, y=0, text='', explicitly_set=True)
|
||||
ac(x, 0, is_multicell=True, width=2, scale=2, subscale=3, x=x, y=0, text='', natural_width=False)
|
||||
for x in range(0, 4):
|
||||
ac(x, 1, is_multicell=True, width=2, scale=2, subscale=3, x=x, y=1, text='', explicitly_set=True)
|
||||
ac(x, 1, is_multicell=True, width=2, scale=2, subscale=3, x=x, y=1, text='', natural_width=False)
|
||||
|
||||
# Test draw with cursor in a multicell
|
||||
s.reset()
|
||||
|
|
@ -148,16 +153,42 @@ def test_multicell(self: TestMulticell) -> None:
|
|||
self.ae(str(s.line(0)), ' b')
|
||||
|
||||
# Test multicell with cursor in a multicell
|
||||
def big_a(x, y):
|
||||
def big_a(x, y=0, spaces=False, skip=False):
|
||||
s.reset()
|
||||
s.cursor.x, s.cursor.y = 1, 1
|
||||
multicell(s, 'a', scale=4)
|
||||
ac(1, 1, x=0, y=0, text='a', scale=4, width=1)
|
||||
s.cursor.x, s.cursor.y = x, y
|
||||
multicell(s, 'b', scale=2)
|
||||
self.ae(4, count_multicells())
|
||||
for x in range(1, 5):
|
||||
ac(x, 4, text=' ')
|
||||
big_a(0, 0), big_a(1, 1), big_a(2, 2), big_a(5, 1)
|
||||
if skip:
|
||||
self.ae(20, count_multicells())
|
||||
assert_cursor_at(2, 4)
|
||||
self.assertIn('a', str(s.linebuf))
|
||||
else:
|
||||
ac(x, y, text='b')
|
||||
self.ae(4, count_multicells())
|
||||
for x_ in range(1, 5):
|
||||
ac(x_, 4, text=' ' if spaces else '')
|
||||
for y in (0, 1):
|
||||
big_a(0, y), big_a(1, y), big_a(2, y, spaces=True)
|
||||
big_a(2, 2, skip=True), big_a(5, 1, skip=True)
|
||||
|
||||
# Test multicell with combining and flag codepoints and default width
|
||||
def seq(text, *expected):
|
||||
s.reset()
|
||||
multicell(s, text)
|
||||
i = iter(expected)
|
||||
for x in range(s.cursor.x):
|
||||
cell = s.cpu_cells(0, x)
|
||||
if cell['x'] == 0:
|
||||
q = next(i)
|
||||
ac(x, 0, text=q, width=wcswidth(q))
|
||||
seq('ab', 'a', 'b')
|
||||
flag = '\U0001f1ee\U0001f1f3'
|
||||
seq(flag + 'CD', flag, 'C', 'D')
|
||||
seq('àn̂X', 'à', 'n̂', 'X')
|
||||
seq('\U0001f1eea', '\U0001f1ee', 'a')
|
||||
del flag, seq
|
||||
|
||||
# Test insert chars with multicell (aka right shift)
|
||||
s.reset()
|
||||
|
|
@ -329,8 +360,8 @@ def test_multicell(self: TestMulticell) -> None:
|
|||
s.erase_in_display(22)
|
||||
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(1).as_ansi(), f'a\x1b]{TEXT_SIZE_CODE};w=1:s=2;b\x07c')
|
||||
self.ae(s.historybuf.line(0).as_ansi(), ' ')
|
||||
|
||||
# Insert lines
|
||||
s.reset()
|
||||
|
|
@ -355,3 +386,33 @@ def test_multicell(self: TestMulticell) -> None:
|
|||
s.delete_lines(1)
|
||||
for y in range(s.lines):
|
||||
assert_line('\0' * s.columns, y)
|
||||
|
||||
# ansi output
|
||||
def ta(expected):
|
||||
actual = as_ansi().rstrip()[3:]
|
||||
self.ae(expected, actual)
|
||||
s.reset()
|
||||
parse_bytes(s, actual.encode())
|
||||
actual2 = as_ansi().rstrip()[3:]
|
||||
self.ae(expected, actual2)
|
||||
s.reset()
|
||||
|
||||
s.reset()
|
||||
s.draw('a')
|
||||
multicell(s, 'b', width=2)
|
||||
s.draw('c')
|
||||
ta('a\x1b]66;w=2;b\x07c')
|
||||
multicell(s, 'a')
|
||||
s.cursor.fg = as_rgb(0xffffff)
|
||||
multicell(s, 'b')
|
||||
ta('a\x1b[38:2:255:255:255mb')
|
||||
multicell(s, 'a', scale=2)
|
||||
multicell(s, 'b', scale=2)
|
||||
ta('\x1b]66;w=1:s=2;ab\x07')
|
||||
multicell(s, 'a', scale=2)
|
||||
s.cursor.fg = as_rgb(0xffffff)
|
||||
multicell(s, 'b', scale=2)
|
||||
ta('\x1b]66;w=1:s=2;a\x07\x1b[38:2:255:255:255m\x1b]66;w=1:s=2;b\x07\n\x1b[m\x1b[38:2:255:255:255m')
|
||||
multicell(s, 'a', scale=3)
|
||||
multicell(s, 'b', scale=2)
|
||||
ta('\x1b]66;w=1:s=3;a\x07\x1b]66;w=1:s=2;b\x07')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue