Files
Kovid Goyal 561686c9d4 Refactor rewrap code, again
Now we do the rewrap of history and line buffers together. This is
faster and deals with multiline chars split between the two buffers
correctly. Also, considerably simpler code.
2025-02-03 10:56:49 +05:30

318 lines
12 KiB
C

/*
* resize.c
* Copyright (C) 2024 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include "resize.h"
#include "lineops.h"
typedef struct Rewrap {
struct {
LineBuf *lb;
HistoryBuf *hb;
index_type x, y, hb_count;
Line line, scratch_line;
} src, dest;
ANSIBuf *as_ansi_buf;
TrackCursor *cursors;
LineBuf *sb;
index_type num_content_lines_before, src_x_limit;
bool prev_src_line_ended_with_wrap, current_src_line_has_multline_cells, current_dest_line_has_multiline_cells;
bool dest_line_from_linebuf, src_is_in_linebuf;
} Rewrap;
static void
setup_line(TextCache *tc, index_type xnum, Line *l) {
l->text_cache = tc;
l->xnum = xnum;
}
#define src_xnum (r->src.lb->xnum)
#define dest_xnum (r->dest.lb->xnum)
static void
exclude_empty_lines_at_bottom(Rewrap *r) {
index_type first, i;
bool is_empty = true;
// Find the first line that contains some content
#define self (r->src.lb)
first = self->ynum;
do {
first--;
CPUCell *cells = linebuf_cpu_cells_for_line(self, first);
for(i = 0; i < self->xnum; i++) {
if (cells[i].ch_or_idx || cells[i].ch_is_idx) { is_empty = false; break; }
}
} while(is_empty && first > 0);
if (!is_empty) r->num_content_lines_before = first + 1;
#undef self
}
static void
init_src_line_basic(Rewrap *r, index_type y, Line *dest, bool update_state) {
if (r->src_is_in_linebuf) {
linebuf_init_line_at(r->src.lb, y - r->src.hb_count, dest);
} else if (r->src.y >= r->src.hb_count) {
if (update_state) r->src_is_in_linebuf = true;
linebuf_init_line_at(r->src.lb, y - r->src.hb_count, dest);
} else {
// historybuf_init_line uses reverse indexing
historybuf_init_line(r->src.hb, r->src.hb->count - y - 1, dest);
}
}
static bool
init_src_line(Rewrap *r) {
bool newline_needed = !r->prev_src_line_ended_with_wrap;
init_src_line_basic(r, r->src.y, &r->src.line, true);
r->src_x_limit = src_xnum;
r->prev_src_line_ended_with_wrap = r->src.line.cpu_cells[src_xnum - 1].next_char_was_wrapped;
r->src.line.cpu_cells[src_xnum - 1].next_char_was_wrapped = false;
// Trim trailing blanks
while (r->src_x_limit && r->src.line.cpu_cells[r->src_x_limit - 1].ch_and_idx == BLANK_CHAR) r->src_x_limit--;
r->src.x = 0;
r->current_src_line_has_multline_cells = false;
for (index_type i = 0; i < r->src_x_limit; i++) if (r->src.line.cpu_cells[i].is_multicell && r->src.line.cpu_cells[i].scale > 1) {
r->current_src_line_has_multline_cells = true;
break;
}
return newline_needed;
}
#define set_dest_line_attrs(dest_y) r->dest.lb->line_attrs[dest_y] = r->src.line.attrs; r->src.line.attrs.prompt_kind = UNKNOWN_PROMPT_KIND;
static void
first_dest_line(Rewrap *r) {
if (r->src.hb_count) historybuf_next_dest_line(r->dest.hb, r->as_ansi_buf, &r->src.line, 0, &r->dest.line, false);
else {
r->dest_line_from_linebuf = true;
linebuf_init_line_at(r->dest.lb, 0, &r->dest.line);
set_dest_line_attrs(0);
}
}
static index_type
linebuf_next_dest_line(Rewrap *r, bool continued) {
#define dest_y r->dest.y
LineBuf *dest = r->dest.lb;
linebuf_set_last_char_as_continuation(dest, dest_y, continued);
if (dest_y >= dest->ynum - 1) {
linebuf_index(dest, 0, dest->ynum - 1);
if (r->dest.hb != NULL) {
linebuf_init_line(dest, dest->ynum - 1);
dest->line->attrs.has_dirty_text = true;
historybuf_add_line(r->dest.hb, dest->line, r->as_ansi_buf);
}
linebuf_clear_line(dest, dest->ynum - 1, true);
} else dest_y++;
linebuf_init_line_at(dest, dest_y, &r->dest.line);
set_dest_line_attrs(dest_y);
return dest_y;
#undef dest_y
}
static void
next_dest_line(Rewrap *r, bool continued) {
r->dest.x = 0;
r->current_dest_line_has_multiline_cells = false;
if (r->dest_line_from_linebuf) {
r->dest.y = linebuf_next_dest_line(r, continued);
} else if (r->src_is_in_linebuf) {
r->dest_line_from_linebuf = true;
r->dest.y = 0;
linebuf_init_line_at(r->dest.lb, 0, &r->dest.line);
set_dest_line_attrs(0);
if (continued && r->dest.hb && r->dest.hb->count) {
historybuf_init_line(r->dest.hb, 0, r->dest.hb->line);
r->dest.hb->line->cpu_cells[dest_xnum-1].next_char_was_wrapped = true;
}
} else {
r->dest.y = historybuf_next_dest_line(r->dest.hb, r->as_ansi_buf, &r->src.line, r->dest.y, &r->dest.line, continued);
}
if (r->sb->line_attrs[0].has_dirty_text) {
CPUCell *cpu_cells; GPUCell *gpu_cells;
linebuf_init_cells(r->sb, 0, &cpu_cells, &gpu_cells);
memcpy(r->dest.line.cpu_cells, cpu_cells, dest_xnum * sizeof(cpu_cells[0]));
memcpy(r->dest.line.gpu_cells, gpu_cells, dest_xnum * sizeof(gpu_cells[0]));
r->current_dest_line_has_multiline_cells = true;
}
linebuf_index(r->sb, 0, r->sb->ynum - 1);
if (r->sb->line_attrs[r->sb->ynum - 1].has_dirty_text) {
linebuf_clear_line(r->sb, r->sb->ynum - 1, true);
}
}
static void
update_tracked_cursors(Rewrap *r, index_type num_cells, index_type src_y, index_type dest_y, index_type x_limit) {
if (!r->src_is_in_linebuf) return;
src_y -= r->src.hb_count;
for (TrackCursor *t = r->cursors; !t->is_sentinel; t++) {
if (t->y == src_y && r->src.x <= t->x && (t->x < r->src.x + num_cells || t->x >= x_limit)) {
t->dest_y = dest_y;
t->dest_x = r->dest.x + (t->x - r->src.x);
if (t->dest_x > dest_xnum) t->dest_x = dest_xnum;
}
}
}
static bool
find_space_in_dest_line(Rewrap *r, index_type num_cells) {
while (r->dest.x + num_cells <= dest_xnum) {
index_type before = r->dest.x;
for (index_type x = r->dest.x; x < r->dest.x + num_cells; x++) {
if (r->dest.line.cpu_cells[x].is_multicell) {
r->dest.x = x + mcd_x_limit(r->dest.line.cpu_cells + x);
break;
}
}
if (before == r->dest.x) return true;
}
return false;
}
static void
find_space_in_dest(Rewrap *r, index_type num_cells) {
while (!find_space_in_dest_line(r, num_cells)) next_dest_line(r, true);
}
static void
copy_range(Line *src, index_type src_at, Line* dest, index_type dest_at, index_type num) {
memcpy(dest->cpu_cells + dest_at, src->cpu_cells + src_at, num * sizeof(CPUCell));
memcpy(dest->gpu_cells + dest_at, src->gpu_cells + src_at, num * sizeof(GPUCell));
}
static void
copy_multiline_extra_lines(Rewrap *r, CPUCell *src_cell, index_type mc_width) {
for (index_type i = 1; i < src_cell->scale; i++) {
init_src_line_basic(r, r->src.y + i, &r->src.scratch_line, false);
linebuf_init_line_at(r->sb, i - 1, &r->dest.scratch_line);
linebuf_mark_line_dirty(r->sb, i - 1);
copy_range(&r->src.scratch_line, r->src.x, &r->dest.scratch_line, r->dest.x, mc_width);
update_tracked_cursors(r, mc_width, r->src.y + i, r->dest.y + i, src_xnum + 10000 /* ensure cursor is moved only if in region being copied */);
}
}
static void
multiline_copy_src_to_dest(Rewrap *r) {
CPUCell *c; index_type mc_width;
while (r->src.x < r->src_x_limit) {
c = &r->src.line.cpu_cells[r->src.x];
if (c->is_multicell) {
mc_width = mcd_x_limit(c);
if (mc_width > dest_xnum) {
update_tracked_cursors(r, mc_width, r->src.y, r->dest.y, r->src_x_limit);
r->src.x += mc_width;
continue;
} else if (c->y) {
r->src.x += mc_width;
continue;
}
} else mc_width = 1;
find_space_in_dest(r, mc_width);
copy_range(&r->src.line, r->src.x, &r->dest.line, r->dest.x, mc_width);
update_tracked_cursors(r, mc_width, r->src.y, r->dest.y, r->src_x_limit);
if (c->scale > 1) copy_multiline_extra_lines(r, c, mc_width);
r->src.x += mc_width; r->dest.x += mc_width;
}
}
static void
fast_copy_src_to_dest(Rewrap *r) {
CPUCell *c; index_type mc_width;
while (r->src.x < r->src_x_limit) {
if (r->dest.x >= dest_xnum) {
next_dest_line(r, true);
if (r->current_dest_line_has_multiline_cells) {
multiline_copy_src_to_dest(r);
return;
}
}
index_type num = MIN(r->src_x_limit - r->src.x, dest_xnum - r->dest.x);
if (num && (c = &r->src.line.cpu_cells[r->src.x + num - 1])->is_multicell && c->x != (mc_width = mcd_x_limit(c)) - 1) {
// we have a split multicell at the right edge of the copy region
if (num > mc_width) num = MIN(r->src_x_limit - r->src.x - mc_width, num);
else {
if (mc_width > dest_xnum) {
multiline_copy_src_to_dest(r);
return;
}
r->dest.x = dest_xnum;
continue;
}
}
copy_range(&r->src.line, r->src.x, &r->dest.line, r->dest.x, num);
update_tracked_cursors(r, num, r->src.y, r->dest.y, r->src_x_limit);
r->src.x += num; r->dest.x += num;
}
}
static void
rewrap(Rewrap *r) {
r->src.hb_count = r->src.hb ? r->src.hb->count : 0;
// Fast path
if (r->dest.lb->xnum == r->src.lb->xnum && r->dest.lb->ynum == r->src.lb->ynum) {
memcpy(r->dest.lb->line_map, r->src.lb->line_map, sizeof(index_type) * r->src.lb->ynum);
memcpy(r->dest.lb->line_attrs, r->src.lb->line_attrs, sizeof(LineAttrs) * r->src.lb->ynum);
memcpy(r->dest.lb->cpu_cell_buf, r->src.lb->cpu_cell_buf, (size_t)r->src.lb->xnum * r->src.lb->ynum * sizeof(CPUCell));
memcpy(r->dest.lb->gpu_cell_buf, r->src.lb->gpu_cell_buf, (size_t)r->src.lb->xnum * r->src.lb->ynum * sizeof(GPUCell));
r->num_content_lines_before = r->src.lb->ynum;
if (r->dest.hb && r->src.hb) historybuf_fast_rewrap(r->dest.hb, r->src.hb);
r->dest.y = r->src.lb->ynum - 1;
return;
}
setup_line(r->src.lb->text_cache, src_xnum, &r->src.line);
setup_line(r->src.lb->text_cache, dest_xnum, &r->dest.line);
setup_line(r->src.lb->text_cache, src_xnum, &r->src.scratch_line);
setup_line(r->src.lb->text_cache, dest_xnum, &r->dest.scratch_line);
exclude_empty_lines_at_bottom(r);
for (; r->src.y < r->num_content_lines_before + r->src.hb_count; r->src.y++) {
if (init_src_line(r)) {
if (r->src.y) next_dest_line(r, false);
else first_dest_line(r);
}
if (r->current_src_line_has_multline_cells || r->current_dest_line_has_multiline_cells) multiline_copy_src_to_dest(r);
else fast_copy_src_to_dest(r);
}
}
ResizeResult
resize_screen_buffers(LineBuf *lb, HistoryBuf *hb, index_type lines, index_type columns, ANSIBuf *as_ansi_buf, TrackCursor *cursors) {
ResizeResult ans = {0};
ans.lb = alloc_linebuf(lines, columns, lb->text_cache);
if (!ans.lb) return ans;
RAII_PyObject(raii_nlb, (PyObject*)ans.lb); (void) raii_nlb;
if (hb) {
ans.hb = historybuf_alloc_for_rewrap(columns, hb);
if (!ans.hb) return ans;
}
RAII_PyObject(raii_nhb, (PyObject*)ans.hb); (void) raii_nhb;
Rewrap r = {
.src = {.lb=lb, .hb=hb}, .dest = {.lb=ans.lb, .hb=ans.hb},
.as_ansi_buf = as_ansi_buf, .cursors = cursors,
};
r.sb = alloc_linebuf(SCALE_BITS << 1, columns, lb->text_cache);
if (!r.sb) return ans;
RAII_PyObject(scratch, (PyObject*)r.sb); (void)scratch;
for (TrackCursor *t = cursors; !t->is_sentinel; t++) { t->dest_x = t->x; t->dest_y = t->y; }
rewrap(&r);
ans.num_content_lines_before = r.num_content_lines_before;
ans.num_content_lines_after = MIN(r.dest.y + 1, ans.lb->ynum);
if (hb) historybuf_finish_rewrap(ans.hb, hb);
for (unsigned i = 0; i < ans.num_content_lines_after; i++) linebuf_mark_line_dirty(ans.lb, i);
for (TrackCursor *t = cursors; !t->is_sentinel; t++) { t->dest_x = MIN(t->dest_x, columns); t->dest_y = MIN(t->dest_y, lines); }
Py_INCREF(raii_nlb); Py_XINCREF(raii_nhb);
ans.ok = true;
return ans;
}